├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── docs ├── _static │ ├── .gitignore │ └── slowquant.png ├── autodoc │ ├── hartreefock.rst │ ├── molecularintegrals.rst │ ├── molecule.rst │ ├── qiskit_custom_ansatz.rst │ ├── qiskit_interface.rst │ ├── qiskit_linear_response.rst │ ├── qiskit_operators_circuits.rst │ ├── qiskit_util.rst │ ├── qiskit_wavefunction.rst │ ├── slowquant.rst │ ├── unitary_coupled_cluster_lr.rst │ ├── unitary_coupled_cluster_optimizers.rst │ ├── unitary_coupled_cluster_util.rst │ └── unitary_coupled_cluster_wf.rst ├── conf.py ├── index.rst ├── install.rst └── requirements.txt ├── pyproject.toml ├── slowquant ├── SlowQuant.py ├── __init__.py ├── hartreefock │ ├── diis.py │ ├── hartreefock_in_memory.py │ ├── hartreefockclass.py │ └── unrestricted_hartreefock_in_memory.py ├── logger.py ├── molecularintegrals │ ├── __init__.py │ ├── electronrepulsion.py │ ├── integralclass.py │ ├── integralfunctions.py │ ├── kineticenergy.py │ ├── multipole.py │ └── nuclearattraction.py ├── molecule │ ├── __init__.py │ ├── basis_reader.py │ ├── basisset │ │ ├── 4-31g.basis │ │ ├── 6-31++g.basis │ │ ├── 6-311g.basis │ │ ├── 6-311g_st__st_.basis │ │ ├── 6-31g.basis │ │ ├── 6-31g_st_.basis │ │ ├── 6-31g_st__st_.basis │ │ ├── aug-cc-pvdz.basis │ │ ├── aug-cc-pvtz.basis │ │ ├── cc-pvdz.basis │ │ ├── cc-pvtz.basis │ │ ├── def2-svp.basis │ │ ├── dz.basis │ │ ├── dzp.basis │ │ └── sto-3g.basis │ ├── constants.py │ ├── moleculeclass.py │ └── moleculefunctions.py ├── qiskit_interface │ ├── __init__.py │ ├── circuit_wavefunction.py │ ├── custom_ansatz.py │ ├── interface.py │ ├── linear_response │ │ ├── allprojected.py │ │ ├── lr_baseclass.py │ │ ├── naive.py │ │ └── projected.py │ ├── operators_circuits.py │ └── util.py └── unitary_coupled_cluster │ ├── __init__.py │ ├── ci_spaces.py │ ├── density_matrix.py │ ├── fermionic_operator.py │ ├── linear_response │ ├── __init__.py │ ├── allprojected.py │ ├── allselfconsistent.py │ ├── allstatetransfer.py │ ├── lr_baseclass.py │ ├── naive.py │ ├── projected.py │ ├── projected_statetransfer.py │ ├── selfconsistent.py │ └── statetransfer.py │ ├── operator_state_algebra.py │ ├── operators.py │ ├── optimizers.py │ ├── sa_ups_wavefunction.py │ ├── ucc_wavefunction.py │ ├── ups_wavefunction.py │ └── util.py └── tests ├── test_hartreefock.py ├── test_integral.py ├── test_oscillator_strength.py ├── test_properties.py ├── test_qiskit_interface.py ├── test_qiskit_unitary_product_state.py ├── test_unitary_coupled_cluster.py ├── test_unitary_coupled_cluster_all.py ├── test_unitary_product_state.py └── test_unrestrictedhartreefock.py /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .ipynb_checkpoints/ 3 | .pytest_cache/ 4 | __pycache__/ 5 | local_documentation/ 6 | test_h2_save.npz 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v5.0.0 5 | hooks: 6 | - id: check-added-large-files 7 | - id: check-case-conflict 8 | - id: check-docstring-first 9 | - id: check-yaml 10 | - id: end-of-file-fixer 11 | - id: forbid-new-submodules 12 | - id: file-contents-sorter 13 | files: ".gitignore" 14 | - id: mixed-line-ending 15 | - id: requirements-txt-fixer 16 | - id: trailing-whitespace 17 | 18 | - repo: https://github.com/pre-commit/mirrors-mypy 19 | rev: v1.15.0 20 | hooks: 21 | - id: mypy 22 | 23 | - repo: https://github.com/astral-sh/ruff-pre-commit 24 | rev: v0.11.10 25 | hooks: 26 | # Run the linter. 27 | - id: ruff-check 28 | args: [ --fix ] 29 | # Run the formatter. 30 | - id: ruff-format 31 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.12" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Erik Kjellgren 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Documentation Status](https://readthedocs.org/projects/slowquant/badge/?version=latest)](http://slowquant.readthedocs.io/en/latest/?badge=latest) 2 | 3 | # SlowQuant 4 | 5 | ![SlowQuant logo](https://cloud.githubusercontent.com/assets/11976167/26658726/5e125b02-466c-11e7-8790-8412789fc9fb.jpg) 6 | 7 | SlowQuant is a molecular quantum chemistry program written in Python for classic and quantum computing. 8 | Its specialty is unitary coupled cluster and (time-dependent) linear response in various novel parametrization schemes. 9 | Even the computational demanding parts are written in Python, so it lacks speed, thus the name SlowQuant. 10 | 11 | Documentation can be found at: 12 | 13 | http://slowquant.readthedocs.io/en/latest/ 14 | 15 | ## Quantum Computing, Variational Quantum Eigensolver 16 | 17 | - tUCCSD (trotterized UCCSD through Qiskit) 18 | - fUCCSD (factorized UCCSD) 19 | - tUPS (tiled Unitary Product State) 20 | - Naive Linear Response SD, with singlet spin-adapted operators 21 | - Projected Linear Response SD, with singlet spin-adapted operators 22 | 23 | These features are also implemented with the active-space approximation and orbital-optimization. 24 | Suitable for ideal simulator, shot noise simulator, or quantum hardware via IBM Quantum Hub (Interface via Qiskit) 25 | 26 | ## Conventional Computing, Unitary Coupled Cluster 27 | 28 | Current implementation supports: 29 | 30 | - UCCSD, spin-adapted operators 31 | - UCCSDTQ56 32 | - Linear Response SD, spin-adapted operators 33 | - Linear Response SDTQ56 34 | 35 | These features are also implemented with the active-space approximation and orbital-optimization. 36 | 37 | ## Usual features 38 | 39 | SlowQuant also got some conventional methods, such as Hartree-Fock and molecular integrals. 40 | Just use [PySCF](https://github.com/pyscf/pyscf) instead. 41 | 42 | ## Cited in 43 | 44 | - Kjellgren, E. R., Ziems, K. M., Reinholdt, P., Sauer, S., Coriani, S., & Kongsted, J. (2025). Exact closed-form expression for unitary spin-adapted fermionic singlet double excitation operators. arXiv preprint arXiv:2505.00883. 45 | - Jensen, P. W., Hedemark, G. S., Ziems, K. M., Kjellgren, E. R., Reinholdt, P., Knecht, S., ... & Sauer, S. (2025). Hyperfine Coupling Constants on Quantum Computers: Performance, Errors, and Future Prospects. arXiv preprint arXiv:2503.09214. 46 | - Ziems, K. M., Kjellgren, E. R., Sauer, S. P., Kongsted, J., & Coriani, S. (2025). Understanding and mitigating noise in molecular quantum linear response for spectroscopic properties on quantum computers. Chemical Science. 47 | - Kjellgren, E. R., Reinholdt, P., Ziems, K. M., Sauer, S., Coriani, S., & Kongsted, J. (2024). Divergences in classical and quantum linear response and equation of motion formulations. The Journal of Chemical Physics, 161(12). 48 | - von Buchwald, T. J., Ziems, K. M., Kjellgren, E. R., Sauer, S. P., Kongsted, J., & Coriani, S. (2024). Reduced density matrix formulation of quantum linear response. Journal of Chemical Theory and Computation, 20(16), 7093-7101. 49 | - Chan, M., Verstraelen, T., Tehrani, A., Richer, M., Yang, X. D., Kim, T. D., ... & Ayers, P. W. (2024). The tale of HORTON: Lessons learned in a decade of scientific software development. The Journal of Chemical Physics, 160(16). 50 | - Ziems, K. M., Kjellgren, E. R., Reinholdt, P., Jensen, P. W., Sauer, S. P., Kongsted, J., & Coriani, S. (2024). Which options exist for NISQ-friendly linear response formulations?. Journal of Chemical Theory and Computation, 20(9), 3551-3565. 51 | - Chaves, B. D. P. G. (2023). Desenvolvimentos em python aplicados ao ensino da química quântica. 52 | - Lehtola, S., & Karttunen, A. J. (2022). Free and open source software for computational chemistry education. Wiley Interdisciplinary Reviews: Computational Molecular Science, 12(5), e1610. 53 | 54 | ## Feature Graveyard 55 | 56 | | Feature | Last living commit | 57 | |------------------------------------------------|------------------------------------------| 58 | | RDM trace correction quantum wave function | e26074fc8aae8dc0f6528308022ad265c5ca18bc | 59 | | No submatrix saving in proj and all-proj LR | 3f5df6818c4dbbb2b54606d0a1a4e00badfb766d | 60 | | Approxmiate Hermitification in linear response | 3f5df6818c4dbbb2b54606d0a1a4e00badfb766d | 61 | | Approxmiate linear response formalism | 3f5df6818c4dbbb2b54606d0a1a4e00badfb766d | 62 | | KS-DFT | 1b9c5669ab72dfceee0a69c8dca1c67dd4b31bfd | 63 | | MP2 | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 64 | | RPA | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 65 | | Geometry Optimization | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 66 | | CIS | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 67 | | CCSD | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 68 | | CCSD(T) | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 69 | | BOMD | 46bf811dfcf217ce0c37ddec77d34ef00da769c3 | 70 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | # empty 2 | -------------------------------------------------------------------------------- /docs/_static/slowquant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikkjellgren/SlowQuant/fbde08b7dd02a5406930e393039c13657bfc3213/docs/_static/slowquant.png -------------------------------------------------------------------------------- /docs/autodoc/hartreefock.rst: -------------------------------------------------------------------------------- 1 | hartreefock 2 | =========== 3 | 4 | .. automodule:: slowquant.hartreefock.hartreefockclass 5 | :members: 6 | 7 | .. automodule:: slowquant.hartreefock.hartreefock_in_memory 8 | :members: 9 | 10 | .. automodule:: slowquant.hartreefock.diis 11 | :members: 12 | -------------------------------------------------------------------------------- /docs/autodoc/molecularintegrals.rst: -------------------------------------------------------------------------------- 1 | molecularintegrals 2 | ================== 3 | 4 | .. automodule:: slowquant.molecularintegrals.integralclass 5 | :members: 6 | 7 | .. automodule:: slowquant.molecularintegrals.integralfunctions 8 | :members: 9 | 10 | .. automodule:: slowquant.molecularintegrals.multipole 11 | :members: 12 | 13 | .. automodule:: slowquant.molecularintegrals.kineticenergy 14 | :members: 15 | 16 | .. automodule:: slowquant.molecularintegrals.nuclearattraction 17 | :members: 18 | 19 | .. automodule:: slowquant.molecularintegrals.electronrepulsion 20 | :members: 21 | -------------------------------------------------------------------------------- /docs/autodoc/molecule.rst: -------------------------------------------------------------------------------- 1 | molecule 2 | ======== 3 | 4 | .. automodule:: slowquant.molecule.moleculeclass 5 | :members: 6 | 7 | .. automodule:: slowquant.molecule.moleculefunctions 8 | :members: 9 | 10 | .. automodule:: slowquant.molecule.constants 11 | :members: 12 | 13 | .. automodule:: slowquant.molecule.basis_reader 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/autodoc/qiskit_custom_ansatz.rst: -------------------------------------------------------------------------------- 1 | Custom Ansatz 2 | ============= 3 | 4 | .. automodule:: slowquant.qiskit_interface.custom_ansatz 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/autodoc/qiskit_interface.rst: -------------------------------------------------------------------------------- 1 | Interface 2 | ========= 3 | 4 | .. automodule:: slowquant.qiskit_interface.interface 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/autodoc/qiskit_linear_response.rst: -------------------------------------------------------------------------------- 1 | Linear Response 2 | =============== 3 | 4 | .. automodule:: slowquant.qiskit_interface.linear_response.allprojected 5 | :members: 6 | 7 | .. automodule:: slowquant.qiskit_interface.linear_response.lr_baseclass 8 | :members: 9 | 10 | .. automodule:: slowquant.qiskit_interface.linear_response.naive 11 | :members: 12 | 13 | .. automodule:: slowquant.qiskit_interface.linear_response.projected 14 | :members: 15 | -------------------------------------------------------------------------------- /docs/autodoc/qiskit_operators_circuits.rst: -------------------------------------------------------------------------------- 1 | Operator Circuits 2 | ================= 3 | 4 | .. automodule:: slowquant.qiskit_interface.operators_circuits 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/autodoc/qiskit_util.rst: -------------------------------------------------------------------------------- 1 | Util 2 | ==== 3 | 4 | .. automodule:: slowquant.qiskit_interface.util 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/autodoc/qiskit_wavefunction.rst: -------------------------------------------------------------------------------- 1 | Wavefunction 2 | ============ 3 | 4 | .. automodule:: slowquant.qiskit_interface.circuit_wavefunction 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/autodoc/slowquant.rst: -------------------------------------------------------------------------------- 1 | SlowQuant (class) 2 | ================= 3 | 4 | .. automodule:: slowquant.SlowQuant 5 | :members: 6 | 7 | .. automodule:: slowquant.logger 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/autodoc/unitary_coupled_cluster_lr.rst: -------------------------------------------------------------------------------- 1 | Unitary Coupled Cluster Linear Response 2 | ======================================= 3 | 4 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.lr_baseclass 5 | :members: 6 | 7 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.allprojected 8 | :members: 9 | 10 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.allselfconsistent 11 | :members: 12 | 13 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.allstatetransfer 14 | :members: 15 | 16 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.naive 17 | :members: 18 | 19 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.projected 20 | :members: 21 | 22 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.projected_statetransfer 23 | :members: 24 | 25 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.selfconsistent 26 | :members: 27 | 28 | .. automodule:: slowquant.unitary_coupled_cluster.linear_response.statetransfer 29 | :members: 30 | -------------------------------------------------------------------------------- /docs/autodoc/unitary_coupled_cluster_optimizers.rst: -------------------------------------------------------------------------------- 1 | Optimizers 2 | ========== 3 | 4 | .. automodule:: slowquant.unitary_coupled_cluster.optimizers 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/autodoc/unitary_coupled_cluster_util.rst: -------------------------------------------------------------------------------- 1 | Unitary Coupled Cluster Util 2 | ============================ 3 | 4 | .. automodule:: slowquant.unitary_coupled_cluster.density_matrix 5 | :members: 6 | 7 | .. automodule:: slowquant.unitary_coupled_cluster.fermionic_operator 8 | :members: 9 | 10 | .. automodule:: slowquant.unitary_coupled_cluster.operator_state_algebra 11 | :members: 12 | 13 | .. automodule:: slowquant.unitary_coupled_cluster.operators 14 | :members: 15 | 16 | .. automodule:: slowquant.unitary_coupled_cluster.ci_spaces 17 | :members: 18 | 19 | .. automodule:: slowquant.unitary_coupled_cluster.util 20 | :members: 21 | -------------------------------------------------------------------------------- /docs/autodoc/unitary_coupled_cluster_wf.rst: -------------------------------------------------------------------------------- 1 | Unitary Coupled Cluster Wave Function 2 | ===================================== 3 | 4 | .. automodule:: slowquant.unitary_coupled_cluster.ucc_wavefunction 5 | :members: 6 | 7 | .. automodule:: slowquant.unitary_coupled_cluster.ups_wavefunction 8 | :members: 9 | 10 | .. automodule:: slowquant.unitary_coupled_cluster.sa_ups_wavefunction 11 | :members: 12 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # pylint: skip-file 4 | 5 | import os 6 | import sys 7 | 8 | sys.path.insert(0, os.path.abspath("..")) 9 | 10 | # -- General configuration ------------------------------------------------ 11 | # If your documentation needs a minimal Sphinx version, state it here. 12 | # needs_sphinx = '1.0' 13 | extensions = [ 14 | "sphinx.ext.mathjax", 15 | "sphinx.ext.autodoc", 16 | "sphinx.ext.napoleon", 17 | "sphinx.ext.autosectionlabel", 18 | ] 19 | 20 | # The suffix(es) of source filenames. 21 | # You can specify multiple suffix as a list of string: 22 | source_suffix = [".rst", ".md"] 23 | 24 | # The master toctree document. 25 | master_doc = "index" 26 | 27 | # General information about the project. 28 | project = "SLOWQUANT" 29 | copyright = "2017-2024, Erik Kjellgren" # pylint: disable=W0622 30 | author = "Erik Kjellgren" 31 | 32 | # -- Options for HTML output ---------------------------------------------- 33 | html_theme = "sphinx_rtd_theme" 34 | html_theme_path = [ 35 | "_themes", 36 | ] 37 | html_static_path = ["_static"] 38 | html_logo = "_static/slowquant.png" 39 | autoclass_content = "both" 40 | autodoc_default_options = {"members": True, "undoc-members": True, "private-members": True} 41 | 42 | # -- Options for LaTeX output --------------------------------------------- 43 | latex_elements = {"preamble": r"\pdfimageresolution=144"} 44 | 45 | latex_documents = [ 46 | (master_doc, "sphinx-example.tex", "sphinx-example Documentation", "Firstname Lastname", "manual"), 47 | ] 48 | 49 | # -- Options for manual page output --------------------------------------- 50 | man_pages = [(master_doc, "sphinx-example", "sphinx-example Documentation", [author], 1)] 51 | 52 | # -- Options for Texinfo output ------------------------------------------- 53 | texinfo_documents = [ 54 | ( 55 | master_doc, 56 | "sphinx-example", 57 | "sphinx-example Documentation", 58 | author, 59 | "sphinx-example", 60 | "One line description of project.", 61 | "Miscellaneous", 62 | ), 63 | ] 64 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | SlowQuant 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: How to use 7 | 8 | install.rst 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Qiskit Interface: 13 | 14 | autodoc/qiskit_interface.rst 15 | autodoc/qiskit_util.rst 16 | autodoc/qiskit_operators_circuits.rst 17 | autodoc/qiskit_wavefunction.rst 18 | autodoc/qiskit_linear_response.rst 19 | autodoc/qiskit_custom_ansatz.rst 20 | 21 | .. toctree:: 22 | :maxdepth: 2 23 | :caption: Classical Unitary Coupled Cluster: 24 | 25 | autodoc/unitary_coupled_cluster_wf.rst 26 | autodoc/unitary_coupled_cluster_lr.rst 27 | autodoc/unitary_coupled_cluster_util.rst 28 | autodoc/unitary_coupled_cluster_optimizers.rst 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | :caption: Conventional Methods: 33 | 34 | autodoc/slowquant.rst 35 | autodoc/molecule.rst 36 | autodoc/molecularintegrals.rst 37 | autodoc/hartreefock.rst 38 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation and Running 2 | ======================== 3 | 4 | How to install SlowQuant, and run the program. 5 | 6 | Compiling documentation 7 | ----------------------- 8 | 9 | The documentation is compiled using sphinx: 10 | 11 | :: 12 | 13 | conda install sphinx 14 | 15 | The theme is downloaded by: 16 | 17 | :: 18 | 19 | pip install sphinx_rtd_theme 20 | 21 | The documentation is now compiled by: 22 | 23 | :: 24 | 25 | sphinx-build docs local_documentation 26 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | numba 2 | numpy 3 | qiskit 4 | qiskit-algorithms 5 | qiskit-nature 6 | scipy 7 | sphinx-rtd-theme==2.0.0 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.ruff] 2 | line-length = 110 3 | indent-width = 4 4 | target-version = "py313" 5 | 6 | # Enable rules to cover flake8, isort, pylint, pydocstyle, etc. 7 | lint.select = [ 8 | "E", # pycodestyle errors (flake8) 9 | "F", # pyflakes (flake8) 10 | "W", # pycodestyle warnings 11 | "I", # isort (import sorting) 12 | "NPY", # NumPy-specific rules 13 | "N", # pep8-naming 14 | "D", # pydocstyle (docstrings) 15 | "PL", # pylint checks 16 | "RUF", # Ruff-specific improvements 17 | ] 18 | 19 | # Optionally ignore some rules you find too strict 20 | lint.ignore = [ 21 | "D100", # undocumented-public-module 22 | "D101", # undocumented-public-class 23 | "D104", # undocumented-public-package 24 | "E501", # line-too-long 25 | "E741", # ambiguous-variable-name 26 | "N801", # invalid-class-name 27 | "N802", # invalid-function-name 28 | "N803", # invalid-argument-name 29 | "N806", # non-lowercase-variable-in-function 30 | "N813", # camelcase-imported-as-lowercase 31 | "N999", # invalid-module-name 32 | "PLR0911", # too-many-return-statements 33 | "PLR0912", # too-many-branches 34 | "PLR0913", # too-many-arguments 35 | "PLR0915", # too-many-statements 36 | "PLR2004", # magic-value-comparison 37 | "PLW2901", # redefined-loop-name 38 | "RUF010", # explicit-f-string-type-conversion 39 | ] 40 | 41 | # Exclude these paths from checks (customize as needed) 42 | exclude = [ 43 | "__pycache__", 44 | ] 45 | 46 | # Allow automatic fixing for all rules 47 | fix = true 48 | 49 | [tool.ruff.lint.pydocstyle] 50 | convention = "google" 51 | 52 | [tool.ruff.format] 53 | # Enable formatting similar to Black 54 | quote-style = "double" 55 | indent-style = "space" 56 | line-ending = "lf" 57 | docstring-code-format = true 58 | skip-magic-trailing-comma = false 59 | -------------------------------------------------------------------------------- /slowquant/SlowQuant.py: -------------------------------------------------------------------------------- 1 | from slowquant.hartreefock.hartreefockclass import _HartreeFock 2 | from slowquant.logger import _Logger 3 | from slowquant.molecularintegrals.integralclass import _Integral 4 | from slowquant.molecule.moleculeclass import _Molecule 5 | 6 | 7 | class SlowQuant: 8 | def __init__(self) -> None: 9 | """Initialize SlowQuant.""" 10 | self.molecule: _Molecule 11 | self.integral: _Integral 12 | self.hartree_fock: _HartreeFock 13 | self.logger = _Logger() 14 | 15 | def set_molecule( 16 | self, 17 | molecule_file: str, 18 | molecular_charge: int = 0, 19 | distance_unit: str = "bohr", 20 | basis_set: str | None = None, 21 | ) -> None: 22 | """Initialize molecule instance. 23 | 24 | Args: 25 | molecule_file: Filename of file containing molecular coordinates. 26 | molecular_charge: Total charge of molecule. 27 | distance_unit: Distance unit used coordinate file. Angstrom or Bohr (default). Internal representatio is Bohr. 28 | basis_set: Name of atom-centered basis set. 29 | """ 30 | self.molecule = _Molecule(molecule_file, molecular_charge, distance_unit) 31 | if basis_set is not None: 32 | self.molecule._set_basis_set(basis_set) 33 | self.integral = _Integral(self.molecule) 34 | 35 | def set_basis_set(self, basis_set: str) -> None: 36 | """Set basis set. 37 | 38 | Args: 39 | basis_set: Name of basis set. 40 | """ 41 | if hasattr(self, "molecule"): 42 | self.molecule._set_basis_set(basis_set) 43 | self.integral = _Integral(self.molecule) 44 | else: 45 | self.logger.add_to_log("Cannot set basis set, molecule is not defined.", is_warning=True) 46 | 47 | def init_hartree_fock(self) -> None: 48 | """Initialize Hartree-Fock module.""" 49 | self.hartree_fock = _HartreeFock(self.molecule, self.integral) 50 | -------------------------------------------------------------------------------- /slowquant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikkjellgren/SlowQuant/fbde08b7dd02a5406930e393039c13657bfc3213/slowquant/__init__.py -------------------------------------------------------------------------------- /slowquant/hartreefock/diis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class DIIS: 5 | def __init__(self, number_bf: int, number_fock_saved_: int = 6) -> None: 6 | """Initialize DIIS-solver. 7 | 8 | Args: 9 | number_bf: Number of basis functions. 10 | number_fock_saved_: Max number of previous Fock matrices saved. 11 | """ 12 | self.fock_matrices: list[np.ndarray] = [] 13 | self.density_matrices: list[np.ndarray] = [] 14 | self.number_fock_saved = number_fock_saved_ 15 | self.num_bf = number_bf 16 | 17 | def get_extrapolated_fock_matrix( 18 | self, fock_matrix: np.ndarray, density_matrix: np.ndarray, S: np.ndarray 19 | ): 20 | r"""Calculate F'. 21 | 22 | Reference: https://github.com/CrawfordGroup/ProgrammingProjects/tree/master/Project%2308 23 | 24 | Args: 25 | fock_matrix: Fock matrix. 26 | density_matrix: Density matrix. 27 | S: Overlap integral matrix. 28 | 29 | Returns: 30 | Extrapolated Fock matrix. 31 | """ 32 | if len(fock_matrix) == 2: 33 | return fock_matrix 34 | self.fock_matrices.append(fock_matrix) 35 | self.density_matrices.append(density_matrix) 36 | 37 | # Cannot extrapolate when only given a single Fock Matrix 38 | if len(self.fock_matrices) == 1: 39 | return fock_matrix 40 | 41 | if len(self.fock_matrices) > self.number_fock_saved: 42 | self.fock_matrices.pop(0) 43 | self.density_matrices.pop(0) 44 | 45 | # Calculate error matrix 46 | error_matrices = np.zeros(np.shape(self.fock_matrices)) 47 | for i, (F, D) in enumerate(zip(self.fock_matrices, self.density_matrices)): 48 | error_matrices[i, :, :] = np.dot(np.dot(F, D), S) - np.dot(np.dot(S, D), F) 49 | 50 | # Build B matrix, and b0 vector 51 | B = np.zeros((len(self.fock_matrices) + 1, len(self.fock_matrices) + 1)) 52 | B[-1, :] = B[:, -1] = -1 53 | B[-1, -1] = 0 54 | b0 = np.zeros(len(self.fock_matrices) + 1) 55 | b0[-1] = -1 56 | for i, error_matrix_i in enumerate(error_matrices): 57 | for j, error_matrix_j in enumerate(error_matrices): 58 | B[i, j] = np.trace(np.dot(error_matrix_i, error_matrix_j)) 59 | 60 | # Solve B, b0 linear equation, and construct Fprime 61 | coefficients = np.linalg.solve(B, b0) 62 | F_extrapolated = np.zeros((self.num_bf, self.num_bf)) 63 | for c, F in zip(coefficients[:-1], self.fock_matrices): 64 | F_extrapolated += c * F 65 | 66 | return F_extrapolated 67 | -------------------------------------------------------------------------------- /slowquant/hartreefock/hartreefock_in_memory.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.hartreefock.diis import DIIS 4 | from slowquant.logger import _Logger 5 | 6 | 7 | def run_hartree_fock( 8 | S: np.ndarray, 9 | T: np.ndarray, 10 | V: np.ndarray, 11 | ERI: np.ndarray, 12 | number_electrons: int, 13 | log: _Logger, 14 | dE_threshold: float, 15 | rmsd_threshold: float, 16 | max_scf_iterations: int, 17 | use_diis: bool = True, 18 | ) -> tuple[float, np.ndarray, np.ndarray, np.ndarray]: 19 | r"""Run restricted Hartree-Fock calculation. 20 | 21 | Reference: https://github.com/CrawfordGroup/ProgrammingProjects/tree/master/Project%2303 22 | """ 23 | # Logging 24 | log.add_to_log(f"{'Iter':^4} {'E_HF':^18} {'DeltaE':^13} {'RMS_D':^12}") 25 | 26 | # Core Hamiltonian 27 | Hcore = T + V 28 | 29 | # Diagonalizing overlap matrix 30 | Lambda_S, L_S = np.linalg.eigh(S) 31 | # Symmetric orthogonal inverse overlap matrix 32 | S_sqrt = np.dot(np.dot(L_S, np.diag(Lambda_S ** (-1 / 2))), np.transpose(L_S)) 33 | 34 | # Initial Density 35 | F0prime = np.dot(np.dot(np.transpose(S_sqrt), Hcore), np.transpose(S_sqrt)) 36 | _, C0prime = np.linalg.eigh(F0prime) 37 | 38 | C0 = np.transpose(np.dot(S_sqrt, C0prime)) 39 | 40 | # Only using occupied MOs 41 | C0 = C0[0 : int(number_electrons / 2)] 42 | D0 = 2 * np.dot(np.transpose(C0), C0) 43 | 44 | # Initial Energy 45 | E0 = np.einsum("ij,ij->", D0, Hcore) 46 | 47 | # Init DIIS 48 | if use_diis: 49 | number_bf = len(S) 50 | diis_acceleration = DIIS(number_bf) 51 | 52 | # SCF iterations 53 | for iteration in range(1, max_scf_iterations + 1): 54 | # New Fock Matrix 55 | J = np.einsum("pqrs,sr->pq", ERI, D0) 56 | K = np.einsum("psqr,sr->pq", ERI, D0) 57 | F = Hcore + J - 0.5 * K 58 | 59 | # Do DIIS acceleration 60 | if use_diis: 61 | F = diis_acceleration.get_extrapolated_fock_matrix(F, D0, S) 62 | 63 | Fprime = np.dot(np.dot(np.transpose(S_sqrt), F), S_sqrt) 64 | _, Cprime = np.linalg.eigh(Fprime) 65 | 66 | C = np.dot(S_sqrt, Cprime) 67 | 68 | CT = np.transpose(C) 69 | CTocc = CT[0 : int(number_electrons / 2)] 70 | Cocc = np.transpose(CTocc) 71 | 72 | D = 2 * np.dot(Cocc, CTocc) 73 | 74 | # New SCF Energy 75 | E = 0.5 * np.einsum("ij,ij->", D, Hcore + F) 76 | 77 | # Convergance 78 | dE = E - E0 79 | rmsd = np.einsum("ij->", (D - D0) ** 2) ** 0.5 80 | 81 | # Logging 82 | log.add_to_log(f"{iteration:>4} {E: 18.12f} {dE: 1.6e} {rmsd:1.6e}") 83 | 84 | D0 = D 85 | E0 = E 86 | if np.abs(dE) < dE_threshold and rmsd < rmsd_threshold: 87 | break 88 | else: 89 | log.add_to_log( 90 | f"Restricted Hartree-Fock did not meet convergence requirements in {max_scf_iterations} iterations", 91 | is_warning=True, 92 | ) 93 | 94 | return E, C, F, D 95 | -------------------------------------------------------------------------------- /slowquant/hartreefock/hartreefockclass.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import slowquant.hartreefock.hartreefock_in_memory as hf_in_mem 4 | import slowquant.hartreefock.unrestricted_hartreefock_in_memory as uhf_in_mem 5 | from slowquant.logger import _Logger 6 | 7 | 8 | class _HartreeFock: 9 | def __init__(self, molecule_obj, integral_obj) -> None: 10 | """Initialize Hartree-Fock class. 11 | 12 | Args: 13 | molecule_obj: Molecule object. 14 | integral_obj: Integral object. 15 | """ 16 | self.mol_obj = molecule_obj 17 | self.int_obj = integral_obj 18 | self.de_threshold = 10**-9 19 | self.rmsd_threshold = 10**-9 20 | self.max_scf_iterations = 100 21 | self.uhf_lumo_homo_mix_coeff = 0.15 22 | self.use_diis = True 23 | self.logger = _Logger() 24 | # Attributes generated from calculations 25 | self.E_hf: float 26 | self.E_uhf: float 27 | self.mo_coeff: np.ndarray 28 | self.rdm1: np.ndarray 29 | self.fock_matrix: np.ndarray 30 | self.mo_coeff_alpha: np.ndarray 31 | self.rdm1_alpha: np.ndarray 32 | self.fock_matrix_alpha: np.ndarray 33 | self.mo_coeff_beta: np.ndarray 34 | self.rdm1_beta: np.ndarray 35 | self.fock_matrix_beta: np.ndarray 36 | 37 | @property 38 | def log(self) -> str: 39 | """Get log.""" 40 | return self.logger.log 41 | 42 | def run_restricted_hartree_fock(self) -> None: 43 | """Run restricted Hartree-Fock calculation.""" 44 | E, C, F, D = hf_in_mem.run_hartree_fock( 45 | self.int_obj.overlap_matrix, 46 | self.int_obj.kinetic_energy_matrix, 47 | self.int_obj.nuclear_attraction_matrix, 48 | self.int_obj.electron_repulsion_tensor, 49 | self.mol_obj.number_electrons, 50 | self.logger, 51 | self.de_threshold, 52 | self.rmsd_threshold, 53 | self.max_scf_iterations, 54 | self.use_diis, 55 | ) 56 | self.E_hf = E 57 | self.mo_coeff = C 58 | self.rdm1 = D 59 | self.fock_matrix = F 60 | 61 | def run_unrestricted_hartree_fock(self) -> None: 62 | """Run restricted Hartree-Fock calculation.""" 63 | E, C_alpha, C_beta, F_alpha, F_beta, D_alpha, D_beta = uhf_in_mem.run_unrestricted_hartree_fock( 64 | self.int_obj.overlap_matrix, 65 | self.int_obj.kinetic_energy_matrix, 66 | self.int_obj.nuclear_attraction_matrix, 67 | self.int_obj.electron_repulsion_tensor, 68 | self.mol_obj.number_electrons_alpha, 69 | self.mol_obj.number_electrons_beta, 70 | self.uhf_lumo_homo_mix_coeff, 71 | self.logger, 72 | self.de_threshold, 73 | self.rmsd_threshold, 74 | self.max_scf_iterations, 75 | self.use_diis, 76 | ) 77 | self.E_uhf = E 78 | self.mo_coeff_alpha = C_alpha 79 | self.mo_coeff_beta = C_beta 80 | self.rdm1_alpha = D_alpha 81 | self.rdm1_beta = D_beta 82 | self.fock_matrix_alpha = F_alpha 83 | self.fock_matrix_beta = F_beta 84 | 85 | @property 86 | def rdm1_charge(self) -> np.ndarray: 87 | r"""Get charge density 1-RDM. 88 | 89 | .. math:: 90 | D_\mathrm{C} = D_\alpha + D_\beta 91 | 92 | Returns: 93 | Charge density 1-RDM. 94 | """ 95 | return self.rdm1_alpha + self.rdm1_beta 96 | 97 | @property 98 | def rdm1_spin(self) -> np.ndarray: 99 | r"""Get spin density 1-RDM. 100 | 101 | .. math:: 102 | D_\mathrm{S} = D_\alpha - D_\beta 103 | 104 | Returns: 105 | Spin density 1-RDM. 106 | """ 107 | return self.rdm1_alpha - self.rdm1_beta 108 | 109 | @property 110 | def spin_contamination(self) -> np.ndarray: 111 | r"""Get spin contamination. 112 | 113 | .. math:: 114 | s = \sum_{i,j}^{N_\alpha,N_beta}\left|S_{ij}^{\alpha\beta}\right|^2 115 | 116 | .. math:: 117 | S_{ij}^{\alpha\beta} = C_{ai}^\alpha C_{bj}^\beta S_{ab}^\mathrm{AO} 118 | 119 | Returns: 120 | Spin density 1-RDM. 121 | """ 122 | spin_contamination = 0.0 123 | S_alpha_beta = np.einsum( 124 | "ai,bj,ab->ij", self.mo_coeff_alpha, self.mo_coeff_beta, self.int_obj.overlap_matrix 125 | ) 126 | for i in range(self.mol_obj.number_electrons_alpha): 127 | for j in range(self.mol_obj.number_electrons_beta): 128 | spin_contamination += S_alpha_beta[i, j] ** 2 129 | return self.mol_obj.number_electrons_beta - spin_contamination 130 | -------------------------------------------------------------------------------- /slowquant/hartreefock/unrestricted_hartreefock_in_memory.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.hartreefock.diis import DIIS 4 | from slowquant.logger import _Logger 5 | 6 | 7 | def run_unrestricted_hartree_fock( 8 | S: np.ndarray, 9 | T: np.ndarray, 10 | V: np.ndarray, 11 | ERI: np.ndarray, 12 | number_electrons_alpha: int, 13 | number_electrons_beta: int, 14 | lumo_homo_mix_coeff: float, 15 | log: _Logger, 16 | dE_threshold: float, 17 | rmsd_threshold: float, 18 | max_scf_iterations: int, 19 | use_diis: bool = True, 20 | ) -> tuple[float, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]: 21 | r"""Run unrestricted Hartree-Fock calculation.""" 22 | # Logging 23 | log.add_to_log( 24 | f"{'Iter':^4} {'E_UHF':^18} {'DeltaE':^13} {'RMS_D_alpha':^12} {'RMS_D_beta':^12}" 25 | ) 26 | 27 | # Core Hamiltonian 28 | Hcore = T + V 29 | 30 | # Diagonalizing overlap matrix 31 | Lambda_S, L_S = np.linalg.eigh(S) 32 | # Symmetric orthogonal inverse overlap matrix 33 | S_sqrt = np.dot(np.dot(L_S, np.diag(Lambda_S ** (-1 / 2))), np.transpose(L_S)) 34 | 35 | # Initial Density 36 | F0prime = np.dot(np.dot(np.transpose(S_sqrt), Hcore), np.transpose(S_sqrt)) 37 | _, C0prime = np.linalg.eigh(F0prime) 38 | 39 | C0_alpha = np.transpose(np.dot(S_sqrt, C0prime)) 40 | C0_beta = np.transpose(np.dot(S_sqrt, C0prime)) 41 | # Break spatial symmetry if system is closed shell 42 | if number_electrons_alpha == number_electrons_beta: 43 | C_HOMO = C0_alpha[number_electrons_alpha - 1] 44 | C_LUMO = C0_alpha[number_electrons_alpha] 45 | C0_alpha[number_electrons_alpha - 1] = ( 46 | 1 / (1 + lumo_homo_mix_coeff**2) ** 0.5 * (C_HOMO + lumo_homo_mix_coeff * C_LUMO) 47 | ) 48 | C0_alpha[number_electrons_alpha] = ( 49 | 1 / (1 + lumo_homo_mix_coeff**2) ** 0.5 * (-lumo_homo_mix_coeff * C_HOMO + C_LUMO) 50 | ) 51 | # Only using occupied MOs 52 | C0_alpha = C0_alpha[0 : int(number_electrons_alpha)] 53 | C0_beta = C0_beta[0 : int(number_electrons_beta)] 54 | D0_alpha = np.dot(np.transpose(C0_alpha), C0_alpha) 55 | D0_beta = np.dot(np.transpose(C0_beta), C0_beta) 56 | 57 | # Initial Energy 58 | E0 = np.einsum("ij,ij->", D0_alpha, Hcore) + np.einsum("ij,ij->", D0_beta, Hcore) 59 | 60 | # Init DIIS 61 | if use_diis: 62 | number_bf = len(S) 63 | diis_acceleration_alpha = DIIS(number_bf) 64 | diis_acceleration_beta = DIIS(number_bf) 65 | 66 | # SCF iterations 67 | for iteration in range(1, max_scf_iterations + 1): 68 | # New Fock Matrix 69 | J_alpha = np.einsum("pqrs,sr->pq", ERI, D0_alpha) 70 | K_alpha = np.einsum("psqr,sr->pq", ERI, D0_alpha) 71 | J_beta = np.einsum("pqrs,sr->pq", ERI, D0_beta) 72 | K_beta = np.einsum("psqr,sr->pq", ERI, D0_beta) 73 | F_alpha = Hcore + J_alpha + J_beta - K_alpha 74 | F_beta = Hcore + J_beta + J_alpha - K_beta 75 | 76 | # Do DIIS acceleration 77 | if use_diis: 78 | F_alpha = diis_acceleration_alpha.get_extrapolated_fock_matrix(F_alpha, D0_alpha, S) 79 | F_beta = diis_acceleration_beta.get_extrapolated_fock_matrix(F_beta, D0_beta, S) 80 | 81 | Fprime_alpha = np.dot(np.dot(np.transpose(S_sqrt), F_alpha), S_sqrt) 82 | Fprime_beta = np.dot(np.dot(np.transpose(S_sqrt), F_beta), S_sqrt) 83 | _, Cprime_alpha = np.linalg.eigh(Fprime_alpha) 84 | _, Cprime_beta = np.linalg.eigh(Fprime_beta) 85 | 86 | C_alpha = np.dot(S_sqrt, Cprime_alpha) 87 | C_beta = np.dot(S_sqrt, Cprime_beta) 88 | 89 | CT_alpha = np.transpose(C_alpha) 90 | CTocc_alpha = CT_alpha[0 : int(number_electrons_alpha)] 91 | Cocc_alpha = np.transpose(CTocc_alpha) 92 | CT_beta = np.transpose(C_beta) 93 | CTocc_beta = CT_beta[0 : int(number_electrons_beta)] 94 | Cocc_beta = np.transpose(CTocc_beta) 95 | 96 | D_alpha = np.dot(Cocc_alpha, CTocc_alpha) 97 | D_beta = np.dot(Cocc_beta, CTocc_beta) 98 | 99 | # New SCF Energy 100 | E = 0.5 * np.einsum("ij,ij->", D_alpha, Hcore + F_alpha) + 0.5 * np.einsum( 101 | "ij,ij->", D_beta, Hcore + F_beta 102 | ) 103 | 104 | # Convergance 105 | dE = E - E0 106 | rmsd_alpha = np.einsum("ij->", (D_alpha - D0_alpha) ** 2) ** 0.5 107 | rmsd_beta = np.einsum("ij->", (D_beta - D0_beta) ** 2) ** 0.5 108 | 109 | # Logging 110 | log.add_to_log( 111 | f"{iteration:>4} {E: 18.12f} {dE: 1.6e} {rmsd_alpha:1.6e} {rmsd_beta:1.6e}" 112 | ) 113 | 114 | D0_alpha = D_alpha 115 | D0_beta = D_beta 116 | E0 = E 117 | if np.abs(dE) < dE_threshold and rmsd_alpha < rmsd_threshold and rmsd_beta < rmsd_threshold: 118 | break 119 | else: 120 | log.add_to_log( 121 | f"Unrestricted Hartree-Fock did not meet convergence requirements in {max_scf_iterations} iterations", 122 | is_warning=True, 123 | ) 124 | 125 | return E, C_alpha, C_beta, F_alpha, F_beta, D_alpha, D_beta 126 | -------------------------------------------------------------------------------- /slowquant/logger.py: -------------------------------------------------------------------------------- 1 | class _Logger: 2 | def __init__(self) -> None: 3 | """Initialize logger.""" 4 | self.log = "" 5 | self.print_output = True 6 | self.print_warnings = True 7 | 8 | def add_to_log(self, content: str, is_warning: bool = False) -> None: 9 | """Add content to log. 10 | 11 | Args: 12 | content: Content to add to log. 13 | is_warning: Log is a warning. 14 | """ 15 | content += "\n" 16 | if is_warning: 17 | content = f"WARNING: {content}" 18 | if self.print_output: 19 | print(content, end="") 20 | self.log.join(content) 21 | elif is_warning and self.print_warnings: 22 | print(content, end="") 23 | self.log.join(content) 24 | self.log.join(content) 25 | -------------------------------------------------------------------------------- /slowquant/molecularintegrals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikkjellgren/SlowQuant/fbde08b7dd02a5406930e393039c13657bfc3213/slowquant/molecularintegrals/__init__.py -------------------------------------------------------------------------------- /slowquant/molecularintegrals/integralclass.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.logger import _Logger 4 | from slowquant.molecularintegrals.electronrepulsion import ( 5 | electron_repulsion_integral_driver, 6 | get_cauchy_schwarz_matrix, 7 | ) 8 | from slowquant.molecularintegrals.kineticenergy import kinetic_energy_integral_driver 9 | from slowquant.molecularintegrals.multipole import multipole_integral_driver 10 | from slowquant.molecularintegrals.nuclearattraction import ( 11 | nuclear_attraction_integral_driver, 12 | ) 13 | from slowquant.molecule.moleculeclass import _Molecule 14 | 15 | 16 | class _Integral: 17 | def __init__(self, molecule_object_: _Molecule) -> None: 18 | """Initialize integral class. 19 | 20 | Args: 21 | molecule_object_: Molecule class object. 22 | """ 23 | self.molecule_object = molecule_object_ 24 | self.store_1e_int = True 25 | self.store_2e_int = True 26 | self.logger = _Logger() 27 | if (self.molecule_object.number_bf) ** 4 * 8 / 10**9 > 6: 28 | self.logger.add_to_log( 29 | f"Storing 2-electron integrals in memory will use approx {self.molecule_object.number_bf**4 * 8 / 10**9:3.1f} GB", 30 | is_warning=True, 31 | ) 32 | self.force_recompute = False 33 | self._overlap_int: np.ndarray | None = None 34 | self._kineticenergy_int: np.ndarray | None = None 35 | self._nuclearattraction_int: np.ndarray | None = None 36 | self._electronrepulsion_int: np.ndarray | None = None 37 | # Integral screening 38 | self._cauchy_schwarz_matrix = get_cauchy_schwarz_matrix(self.molecule_object) 39 | self.cauchy_schwarz_threshold = 10**-12 40 | 41 | @property 42 | def overlap_matrix(self) -> np.ndarray: 43 | """Compute overlap integral matrix. 44 | 45 | Returns: 46 | Overlap integral matrix. 47 | """ 48 | if self.store_1e_int: 49 | if self._overlap_int is None or self.force_recompute: 50 | self._overlap_int = multipole_integral_driver(self.molecule_object, np.array([0, 0, 0])) 51 | return self._overlap_int 52 | # Overlap integral is a special case of multipole integral, 53 | # where the multipole moments are all zero. 54 | return multipole_integral_driver(self.molecule_object, np.array([0, 0, 0])) 55 | 56 | @property 57 | def kinetic_energy_matrix(self) -> np.ndarray: 58 | """Compute kinetic-energy integral matrix. 59 | 60 | Returns: 61 | Kinetic-energy integral matrix. 62 | """ 63 | if self.store_1e_int: 64 | if self._kineticenergy_int is None or self.force_recompute: 65 | self._kineticenergy_int = kinetic_energy_integral_driver(self.molecule_object) 66 | return self._kineticenergy_int 67 | return kinetic_energy_integral_driver(self.molecule_object) 68 | 69 | @property 70 | def nuclear_attraction_matrix(self) -> np.ndarray: 71 | """Compute nuclear-attraction integral matrix. 72 | 73 | Returns: 74 | Nuclear-attraction integral matrix. 75 | """ 76 | if self.store_1e_int: 77 | if self._nuclearattraction_int is None or self.force_recompute: 78 | self._nuclearattraction_int = nuclear_attraction_integral_driver(self.molecule_object) 79 | return self._nuclearattraction_int 80 | return nuclear_attraction_integral_driver(self.molecule_object) 81 | 82 | @property 83 | def electron_repulsion_tensor(self) -> np.ndarray: 84 | """Compute electron-repulsion integral tensor. 85 | 86 | Returns: 87 | Electron-repulsion integral tensor. 88 | """ 89 | if self.store_2e_int: 90 | if self._electronrepulsion_int is None or self.force_recompute: 91 | self._electronrepulsion_int = electron_repulsion_integral_driver( 92 | self.molecule_object, self._cauchy_schwarz_matrix, self.cauchy_schwarz_threshold 93 | ) 94 | return self._electronrepulsion_int 95 | return electron_repulsion_integral_driver( 96 | self.molecule_object, self._cauchy_schwarz_matrix, self.cauchy_schwarz_threshold 97 | ) 98 | 99 | def get_multipole_matrix(self, multipole_order: np.ndarray) -> np.ndarray: 100 | """Compute multipole integral matrix. 101 | 102 | Args: 103 | multipole_order: Cartesian multipole orders (x, y, z). 104 | 105 | Returns: 106 | Multipole integral matrix. 107 | """ 108 | return multipole_integral_driver(self.molecule_object, multipole_order) 109 | -------------------------------------------------------------------------------- /slowquant/molecularintegrals/integralfunctions.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import numpy as np 4 | from numba import float64, int64, jit 5 | 6 | from slowquant.molecule.moleculefunctions import factorial2 7 | 8 | 9 | @jit(float64[:, :, :](float64, float64, float64, float64, int64, int64), nopython=True, cache=True) 10 | def expansion_coefficients( 11 | A_x: float, B_x: float, a: float, b: float, angular_a: int, angular_b: int 12 | ) -> np.ndarray: 13 | r"""Calculate expansion coefficients for McMurchie-Davidson scheme. 14 | 15 | .. math:: 16 | E_t^{i,j} = 0\ \ \mathrm{for}\ \ t<0\ \mathrm{or}\ t>i+j 17 | 18 | .. math:: 19 | E_t^{i+1,j} = \frac{1}{2p}E_{t-1}^{i,j} + X_{PA}E_t^{i,j} + (t+1)E_{t+1}^{i,j} 20 | 21 | .. math:: 22 | E_t^{i,j+1} = \frac{1}{2p}E_{t-1}^{i,j} + X_{PB}E_t^{i,j} + (t+1)E_{t+1}^{i,j} 23 | 24 | .. math:: 25 | E_0^{0,0} = \exp\left(-\mu X^2_{AB}\right) 26 | 27 | With :math:`p=a+b`, :math:`\mu=\frac{ab}{a+b}`, :math:`X_{AB}=A_x-B_x`, :math:`P_x=\frac{aA_x+bB_x}{p}`, and, 28 | for :math:`a` and :math:`b` being Gaussian exponents, and :math:`A_x` :math:`B_x` being the Gaussian centers. 29 | 30 | Reference: Molecular Electronic-Structure Theory, https://onlinelibrary.wiley.com/doi/book/10.1002/9781119019572 31 | 32 | Args: 33 | A_x: x-coordinate of first Gaussian. 34 | B_x: x-coordinate of second Gaussian. 35 | a: Exponent of first Gaussian. 36 | b: Exponent of second Gaussian. 37 | angular_a: x-angular moment of first Gaussian. 38 | angular_b: x-angular moment of second Gaussian. 39 | 40 | Returns: 41 | Returns expansion coefficients for McMurchie-Davidson scheme 42 | """ 43 | X_AB = A_x - B_x 44 | p = a + b 45 | mu = a * b / (a + b) 46 | P_x = (a * A_x + b * B_x) / p 47 | X_PA = P_x - A_x 48 | X_PB = P_x - B_x 49 | e_coeff = np.zeros((angular_a + 1, angular_b + 1, angular_a + angular_b + 1)) 50 | for i in range(angular_a + 1): 51 | for j in range(angular_b + 1): 52 | for t in range(angular_a + angular_b + 1): 53 | value = 0.0 54 | if i == j == t == 0: # Boundary condition 55 | value = np.exp(-mu * X_AB**2) 56 | elif i == 0: # Increment j 57 | if j > 0 and t > 0: 58 | value += 1 / (2 * p) * e_coeff[i, j - 1, t - 1] 59 | if j > 0: 60 | value += X_PB * e_coeff[i, j - 1, t] 61 | if j > 0 and t + 1 <= i + j: 62 | value += (t + 1) * e_coeff[i, j - 1, t + 1] 63 | else: # Increment i 64 | if i > 0 and t > 0: 65 | value += 1 / (2 * p) * e_coeff[i - 1, j, t - 1] 66 | if i > 0: 67 | value += X_PA * e_coeff[i - 1, j, t] 68 | if i > 0 and t + 1 <= i + j: 69 | value += (t + 1) * e_coeff[i - 1, j, t + 1] 70 | e_coeff[i, j, t] = value 71 | return e_coeff 72 | 73 | 74 | @jit(float64(int64, float64), nopython=True, cache=True) 75 | def boys_function(n: int, z: float) -> float: 76 | r"""Calculate Boys function.""" 77 | if n == 0 and z < 10**-10: 78 | F = 1.0 79 | elif n == 0: 80 | F = (np.pi / (4.0 * z)) ** 0.5 * math.erf(z**0.5) 81 | elif z > 25: 82 | # Long range approximation 83 | F = factorial2(2 * n - 1) / (2.0 ** (n + 1.0)) * (np.pi / (z ** (2.0 * n + 1.0))) ** 0.5 84 | elif z < 10**-10: 85 | # special case of z = 0 86 | F = 1.0 / (2.0 * n + 1.0) 87 | else: 88 | F = 0.0 89 | threshold = 10**-12 90 | for i in range(0, 1000): 91 | Fcheck = F 92 | F += (2.0 * z) ** i / (factorial2(2 * n + 2 * i + 1)) 93 | Fcheck -= F 94 | if abs(Fcheck) < threshold: 95 | break 96 | F *= np.exp(-z) * factorial2(2 * n - 1) 97 | return F 98 | 99 | 100 | @jit(float64[:, :, :](int64, int64, int64, float64, float64[:]), nopython=True, cache=True) 101 | def hermite_coulomb_integral( 102 | angular_x: int, angular_y: int, angular_z: int, p: float, PC: np.ndarray 103 | ) -> np.ndarray: 104 | r"""Calculate Hermite coulomb integral. 105 | 106 | .. math:: 107 | R^n_{t+1,u,v} = t*R^{n+1}_{t-1,u,v} + X_{PC}R^{n+1}_{tuv} 108 | 109 | .. math:: 110 | R^n_{t,u+1,v} = u*R^{n+1}_{t,u-1,v} + Y_{PC}R^{n+1}_{tuv} 111 | 112 | .. math:: 113 | R^n_{t,u,v+1} = v*R^{n+1}_{t,u,v-1} + Z_{PC}R^{n+1}_{tuv} 114 | 115 | .. math:: 116 | R^{n}_{000} = (-2p)^nF_n\left(pR^2_{PC}\right) 117 | 118 | Reference: Molecular Electronic-Structure Theory, https://onlinelibrary.wiley.com/doi/book/10.1002/9781119019572 119 | """ 120 | X_PC, Y_PC, Z_PC = PC 121 | R_PC = (X_PC**2 + Y_PC**2 + Z_PC**2) ** 0.5 122 | r_integral = np.zeros( 123 | (angular_x + 1, angular_y + 1, angular_z + 1, angular_x + angular_y + angular_z + 2) 124 | ) 125 | for t in range(angular_x + 1): 126 | for u in range(angular_y + 1): 127 | for v in range(angular_z + 1): 128 | for n in range(angular_x + angular_y + angular_z + 1): 129 | value = 0.0 130 | if t == u == v == 0: 131 | value = (-2.0 * p) ** n * boys_function(n, p * R_PC**2) 132 | elif t == u == 0: # Increment v 133 | if v > 1: 134 | value += (v - 1) * r_integral[t, u, v - 2, n + 1] 135 | if v > 0: 136 | value += Z_PC * r_integral[t, u, v - 1, n + 1] 137 | elif t == 0: # Increment u 138 | if u > 1: 139 | value += (u - 1) * r_integral[t, u - 2, v, n + 1] 140 | if u > 0: 141 | value += Y_PC * r_integral[t, u - 1, v, n + 1] 142 | else: # Increment t 143 | if t > 1: 144 | value += (t - 1) * r_integral[t - 2, u, v, n + 1] 145 | if t > 0: 146 | value += X_PC * r_integral[t - 1, u, v, n + 1] 147 | r_integral[t, u, v, n] = value 148 | return r_integral[:, :, :, 0] 149 | 150 | 151 | def hermite_multipole_integral( 152 | A_x: float, B_x: float, C_x: float, a: float, b: float, multipole_order: int 153 | ) -> np.ndarray: 154 | r"""Calculate Hermite multipole integral. 155 | 156 | .. math:: 157 | M_t^{e+1} = tM_{t-1}^e + X_{PC}M_t^e + \frac{1}{2p}M_{t+1}^e 158 | 159 | .. math:: 160 | M_t^0 = \delta_{t0}\left(\frac{\pi}{p}\right)^(1/2) 161 | 162 | .. math:: 163 | M_t^e = 0,\quad\quad t>e 164 | """ 165 | p = a + b 166 | P_x = (a * A_x + b * B_x) / p 167 | X_PC = P_x - C_x 168 | m_integral = np.zeros((multipole_order + 1, multipole_order + 1)) 169 | for e in range(multipole_order + 1): 170 | for t in range(e + 1): 171 | value = 0.0 172 | if e == 0 and t == 0: 173 | value = (np.pi / p) ** 0.5 174 | elif e == 0: 175 | value = 0.0 176 | else: # Increment e 177 | if t > 0: 178 | value += t * m_integral[e - 1, t - 1] 179 | value += X_PC * m_integral[e - 1, t] 180 | if t + 1 <= e - 1: 181 | value += 1 / (2 * p) * m_integral[e - 1, t + 1] 182 | m_integral[e, t] = value 183 | return m_integral 184 | 185 | 186 | def one_electron_integral_transform(C: np.ndarray, int1e: np.ndarray) -> np.ndarray: 187 | """Transform one-electron integrals from AO to MO. 188 | 189 | Args: 190 | C: MO coefficients. 191 | int1e: One-electron integrals in AO. 192 | 193 | Returns: 194 | One-electron integrals in MO. 195 | """ 196 | return np.einsum("ai,bj,ab->ij", C, C, int1e, optimize=["einsum_path", (0, 2), (0, 1)]) 197 | 198 | 199 | def two_electron_integral_transform(C: np.ndarray, int2e: np.ndarray) -> np.ndarray: 200 | """Transform two-electron integrals from AO to MO. 201 | 202 | Args: 203 | C: MO coefficients. 204 | int2e: Two-electron integrals in AO. 205 | 206 | Returns: 207 | Two-electron integrals in MO. 208 | """ 209 | return np.einsum( 210 | "ai,bj,ck,dl,abcd->ijkl", C, C, C, C, int2e, optimize=["einsum_path", (0, 4), (0, 3), (0, 2), (0, 1)] 211 | ) 212 | -------------------------------------------------------------------------------- /slowquant/molecularintegrals/kineticenergy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.molecularintegrals.integralfunctions import expansion_coefficients 4 | from slowquant.molecule.moleculeclass import _Molecule 5 | 6 | 7 | def kinetic_energy_integral_driver(mol_obj: _Molecule) -> np.ndarray: 8 | """Driver function for calculating kinetic energy integrals. 9 | 10 | Args: 11 | mol_obj: Molecule object. 12 | 13 | Returns: 14 | Kinetic energy integrals. 15 | """ 16 | T = np.zeros((mol_obj.number_bf, mol_obj.number_bf)) 17 | for i, shell1 in enumerate(mol_obj.shells): 18 | for j, shell2 in enumerate(mol_obj.shells): 19 | if j > i: # Matrix is symmetric 20 | break 21 | T_slice = kinetic_energy_integral( 22 | shell1.center, 23 | shell2.center, 24 | shell1.exponents, 25 | shell2.exponents, 26 | shell1.contraction_coefficients, 27 | shell2.contraction_coefficients, 28 | shell1.normalization, 29 | shell2.normalization, 30 | shell1.angular_moments, 31 | shell2.angular_moments, 32 | ) 33 | start_idx1 = shell1.bf_idx[0] 34 | end_idx1 = shell1.bf_idx[-1] + 1 35 | start_idx2 = shell2.bf_idx[0] 36 | end_idx2 = shell2.bf_idx[-1] + 1 37 | T[start_idx1:end_idx1, start_idx2:end_idx2] = T_slice 38 | T[start_idx2:end_idx2, start_idx1:end_idx1] = T_slice.transpose() 39 | return T 40 | 41 | 42 | def kinetic_energy_integral( 43 | center1: np.ndarray, 44 | center2: np.ndarray, 45 | exponents1: np.ndarray, 46 | exponents2: np.ndarray, 47 | contra_coeff1: np.ndarray, 48 | contra_coeff2: np.ndarray, 49 | norm1: np.ndarray, 50 | norm2: np.ndarray, 51 | angular_moments1: np.ndarray, 52 | angular_moments2: np.ndarray, 53 | ) -> np.ndarray: 54 | r"""Calculate kinetic energy integral over shells. 55 | 56 | .. math:: 57 | D^2_{ij} = 4a^2S_{i+2,j} - 2a(2i+1)S_{ij}+i(i-1)S_[i-2,j] 58 | 59 | .. math:: 60 | T_\mathrm{primitive} = -\frac{1}{2}\left( D^2_{ij}S_{kl}S_{mn} + S_{ij}D^2_{kl}S_{mn} + S_{ij}S_{kl}D^2_{mn} \right) 61 | 62 | .. math:: 63 | T = \sum_{ij}N_iN_jc_ic_jT_{\mathrm{primitive},ij} 64 | 65 | With :math:`E` being the expansion coefficients, :math:`p` the sum of the exponents of the Gaussians, :math:`N` the normalization constant, and, :math:`c` the contraction coefficients. 66 | 67 | Reference: Molecular Electronic-Structure Theory, https://onlinelibrary.wiley.com/doi/book/10.1002/9781119019572 68 | 69 | Args: 70 | center1: Center of first shell. 71 | center2: Center of second shell. 72 | exponents1: Exponents of primitives in first shell. 73 | exponents2: Exponents of primitives in second shell. 74 | contra_coeff1: Contraction coefficients of primitives in first shell. 75 | contra_coeff2: Contraction coefficients of primitives in second shell. 76 | norm1: Normalization constant of basis-functions in first shell. 77 | norm2: Normalization constant of basis-functions in second shell. 78 | angular_moments1: Cartesian angular moments of basis-functions in first shell. 79 | angular_moments2: Cartesian angular moments of basis-functions in second shell. 80 | 81 | Returns: 82 | Kinetic energy integral between two shells. 83 | """ 84 | number_primitives1 = len(exponents1) 85 | number_primitives2 = len(exponents2) 86 | number_bf1 = len(angular_moments1) 87 | number_bf2 = len(angular_moments2) 88 | max_ang1 = np.max(angular_moments1) + 2 89 | max_ang2 = np.max(angular_moments2) + 2 90 | T_primitive = np.zeros((number_bf1, number_bf2, number_primitives1, number_primitives2)) 91 | for i in range(number_primitives1): 92 | for j in range(number_primitives2): 93 | E_x = expansion_coefficients( 94 | center1[0], center2[0], exponents1[i], exponents2[j], max_ang1, max_ang2 95 | ) 96 | E_y = expansion_coefficients( 97 | center1[1], center2[1], exponents1[i], exponents2[j], max_ang1, max_ang2 98 | ) 99 | E_z = expansion_coefficients( 100 | center1[2], center2[2], exponents1[i], exponents2[j], max_ang1, max_ang2 101 | ) 102 | for bf_i, (x1, y1, z1) in enumerate(angular_moments1): 103 | temp = norm1[bf_i, i] 104 | for bf_j, (x2, y2, z2) in enumerate(angular_moments2): 105 | D2_x = ( 106 | 4 * exponents1[i] ** 2 * E_x[x1 + 2, x2, 0] 107 | - 2 * exponents1[i] * (2 * x1 + 1) * E_x[x1, x2, 0] 108 | + x1 * (x1 - 1) * E_x[x1 - 2, x2, 0] 109 | ) 110 | D2_y = ( 111 | 4 * exponents1[i] ** 2 * E_y[y1 + 2, y2, 0] 112 | - 2 * exponents1[i] * (2 * y1 + 1) * E_y[y1, y2, 0] 113 | + y1 * (y1 - 1) * E_y[y1 - 2, y2, 0] 114 | ) 115 | D2_z = ( 116 | 4 * exponents1[i] ** 2 * E_z[z1 + 2, z2, 0] 117 | - 2 * exponents1[i] * (2 * z1 + 1) * E_z[z1, z2, 0] 118 | + z1 * (z1 - 1) * E_z[z1 - 2, z2, 0] 119 | ) 120 | T_primitive[bf_i, bf_j, i, j] = ( 121 | temp 122 | * norm2[bf_j, j] 123 | * ( 124 | D2_x * E_y[y1, y2, 0] * E_z[z1, z2, 0] 125 | + E_x[x1, x2, 0] * D2_y * E_z[z1, z2, 0] 126 | + E_x[x1, x2, 0] * E_y[y1, y2, 0] * D2_z 127 | ) 128 | ) 129 | p = exponents1[i] + exponents2[j] 130 | T_primitive[:, :, i, j] *= -1 / 2 * (np.pi / p) ** (3 / 2) 131 | 132 | T_slice = np.einsum("i,j,klij->kl", contra_coeff1, contra_coeff2, T_primitive) 133 | return T_slice 134 | -------------------------------------------------------------------------------- /slowquant/molecularintegrals/multipole.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.molecularintegrals.integralfunctions import ( 4 | expansion_coefficients, 5 | hermite_multipole_integral, 6 | ) 7 | from slowquant.molecule.moleculeclass import _Molecule 8 | 9 | 10 | def multipole_integral_driver(mol_obj: _Molecule, multipole_moment: np.ndarray) -> np.ndarray: 11 | """Driver function for calculating overlap integrals. 12 | 13 | Args: 14 | mol_obj: Molecule object. 15 | multipole_moment: Cartesian multipole moment (x, y, z). 16 | 17 | Returns: 18 | Multipole integrals. 19 | """ 20 | S = np.zeros((mol_obj.number_bf, mol_obj.number_bf)) 21 | for i, shell1 in enumerate(mol_obj.shells): 22 | for j, shell2 in enumerate(mol_obj.shells): 23 | if j > i: # Matrix is symmetric 24 | break 25 | S_slice = multipole_integral( 26 | shell1.center, 27 | shell2.center, 28 | shell1.exponents, 29 | shell2.exponents, 30 | shell1.contraction_coefficients, 31 | shell2.contraction_coefficients, 32 | shell1.normalization, 33 | shell2.normalization, 34 | shell1.angular_moments, 35 | shell2.angular_moments, 36 | mol_obj.center_of_mass, 37 | multipole_moment, 38 | ) 39 | start_idx1 = shell1.bf_idx[0] 40 | end_idx1 = shell1.bf_idx[-1] + 1 41 | start_idx2 = shell2.bf_idx[0] 42 | end_idx2 = shell2.bf_idx[-1] + 1 43 | S[start_idx1:end_idx1, start_idx2:end_idx2] = S_slice 44 | S[start_idx2:end_idx2, start_idx1:end_idx1] = S_slice.transpose() 45 | return S 46 | 47 | 48 | def multipole_integral( 49 | center1: np.ndarray, 50 | center2: np.ndarray, 51 | exponents1: np.ndarray, 52 | exponents2: np.ndarray, 53 | contra_coeff1: np.ndarray, 54 | contra_coeff2: np.ndarray, 55 | norm1: np.ndarray, 56 | norm2: np.ndarray, 57 | angular_moments1: np.ndarray, 58 | angular_moments2: np.ndarray, 59 | multipole_origin: np.ndarray, 60 | multipole_moment: np.ndarray, 61 | ) -> np.ndarray: 62 | r"""Calculate overlap integral over shells. 63 | 64 | .. math:: 65 | S^{efg}_\mathrm{primitive} = \sum_{t=0}^{\mathrm{min}(i+j,e)}\sum_{r=0}^{\mathrm{min}(k+l,f)}\sum_{p=0}^{\mathrm{min}(m+n,g)}E_t^{ij}E_s^{kl}E_k^{mn}M^e_tM^f_rM^g_p 66 | 67 | .. math:: 68 | S = \sum_{ij}N_iN_jc_ic_jS_{\mathrm{primitive},ij} 69 | 70 | With :math:`E` being the expansion coefficients, :math:`p` the sum of the exponents of the Gaussians, :math:`N` the normalization constant, and, :math:`c` the contraction coefficients. 71 | 72 | Reference: Molecular Electronic-Structure Theory, https://onlinelibrary.wiley.com/doi/book/10.1002/9781119019572 73 | 74 | Args: 75 | center1: Center of first shell. 76 | center2: Center of second shell. 77 | exponents1: Exponents of primitives in first shell. 78 | exponents2: Exponents of primitives in second shell. 79 | contra_coeff1: Contraction coefficients of primitives in first shell. 80 | contra_coeff2: Contraction coefficients of primitives in second shell. 81 | norm1: Normalization constant of basis-functions in first shell. 82 | norm2: Normalization constant of basis-functions in second shell. 83 | angular_moments1: Cartesian angular moments of basis-functions in first shell. 84 | angular_moments2: Cartesian angular moments of basis-functions in second shell. 85 | multipole_origin: Origin with respect to the multipole moment. Does not matter for the case when all the moments are zero (overlap integral). 86 | multipole_moment: Cartesian multipole moment (x, y, z). 87 | 88 | Returns: 89 | Overlap integral between two shells. 90 | """ 91 | number_primitives1 = len(exponents1) 92 | number_primitives2 = len(exponents2) 93 | number_bf1 = len(angular_moments1) 94 | number_bf2 = len(angular_moments2) 95 | max_ang1 = np.max(angular_moments1) 96 | max_ang2 = np.max(angular_moments2) 97 | S_primitive = np.zeros((number_bf1, number_bf2, number_primitives1, number_primitives2)) 98 | for i in range(number_primitives1): 99 | for j in range(number_primitives2): 100 | E_x = expansion_coefficients( 101 | center1[0], center2[0], exponents1[i], exponents2[j], max_ang1, max_ang2 102 | ) 103 | E_y = expansion_coefficients( 104 | center1[1], center2[1], exponents1[i], exponents2[j], max_ang1, max_ang2 105 | ) 106 | E_z = expansion_coefficients( 107 | center1[2], center2[2], exponents1[i], exponents2[j], max_ang1, max_ang2 108 | ) 109 | M_x = hermite_multipole_integral( 110 | center1[0], center2[0], multipole_origin[0], exponents1[i], exponents2[j], multipole_moment[0] 111 | ) 112 | M_y = hermite_multipole_integral( 113 | center1[1], center2[1], multipole_origin[1], exponents1[i], exponents2[j], multipole_moment[1] 114 | ) 115 | M_z = hermite_multipole_integral( 116 | center1[2], center2[2], multipole_origin[2], exponents1[i], exponents2[j], multipole_moment[2] 117 | ) 118 | for bf_i, (x1, y1, z1) in enumerate(angular_moments1): 119 | temp = norm1[bf_i, i] 120 | for bf_j, (x2, y2, z2) in enumerate(angular_moments2): 121 | for t1 in range(min(x1 + x2, multipole_moment[0]) + 1): 122 | for t2 in range(min(y1 + y2, multipole_moment[1]) + 1): 123 | for t3 in range(min(z1 + z2, multipole_moment[2]) + 1): 124 | S_primitive[bf_i, bf_j, i, j] += ( 125 | M_x[multipole_moment[0], t1] 126 | * M_y[multipole_moment[1], t2] 127 | * M_z[multipole_moment[2], t3] 128 | * E_x[x1, x2, t1] 129 | * E_y[y1, y2, t2] 130 | * E_z[z1, z2, t3] 131 | ) 132 | S_primitive[bf_i, bf_j, i, j] *= temp * norm2[bf_j, j] 133 | 134 | S_slice = np.einsum("i,j,klij->kl", contra_coeff1, contra_coeff2, S_primitive) 135 | return S_slice 136 | -------------------------------------------------------------------------------- /slowquant/molecularintegrals/nuclearattraction.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.molecularintegrals.integralfunctions import ( 4 | expansion_coefficients, 5 | hermite_coulomb_integral, 6 | ) 7 | from slowquant.molecule.moleculeclass import _Molecule 8 | 9 | 10 | def nuclear_attraction_integral_driver(mol_obj: _Molecule) -> np.ndarray: 11 | """Driver function for calculating nuclear-attraction integrals. 12 | 13 | Args: 14 | mol_obj: Molecule object. 15 | 16 | Returns: 17 | Nuclear-attraction integrals. 18 | """ 19 | V = np.zeros((mol_obj.number_bf, mol_obj.number_bf)) 20 | for i, shell1 in enumerate(mol_obj.shells): 21 | for j, shell2 in enumerate(mol_obj.shells): 22 | if j > i: # Matrix is symmetric 23 | break 24 | V_slice = nuclear_attraction_integral( 25 | shell1.center, 26 | shell2.center, 27 | shell1.exponents, 28 | shell2.exponents, 29 | shell1.contraction_coefficients, 30 | shell2.contraction_coefficients, 31 | shell1.normalization, 32 | shell2.normalization, 33 | shell1.angular_moments, 34 | shell2.angular_moments, 35 | mol_obj.atom_coordinates, 36 | mol_obj.atom_charges, 37 | ) 38 | start_idx1 = shell1.bf_idx[0] 39 | end_idx1 = shell1.bf_idx[-1] + 1 40 | start_idx2 = shell2.bf_idx[0] 41 | end_idx2 = shell2.bf_idx[-1] + 1 42 | V[start_idx1:end_idx1, start_idx2:end_idx2] = V_slice 43 | V[start_idx2:end_idx2, start_idx1:end_idx1] = V_slice.transpose() 44 | return V 45 | 46 | 47 | def nuclear_attraction_integral( 48 | center1: np.ndarray, 49 | center2: np.ndarray, 50 | exponents1: np.ndarray, 51 | exponents2: np.ndarray, 52 | contra_coeff1: np.ndarray, 53 | contra_coeff2: np.ndarray, 54 | norm1: np.ndarray, 55 | norm2: np.ndarray, 56 | angular_moments1: np.ndarray, 57 | angular_moments2: np.ndarray, 58 | atom_coordinates: np.ndarray, 59 | atom_charges: np.ndarray, 60 | ) -> np.ndarray: 61 | r"""Calculate nuclear-attraction integral over shells. 62 | 63 | .. math:: 64 | V_\mathrm{primitive} = -\frac{2\pi}{p}\sum_NZ_N\sum_{tuv}E_t^{ij}E_u^{kl}E_v^{mn}R_{tuv} 65 | 66 | .. math:: 67 | V = \sum_{ij}N_iN_jc_ic_jV_{\mathrm{primitive},ij} 68 | 69 | With :math:`E` being the expansion coefficients, :math:`p` the sum of the exponents of the Gaussians, :math:`N` the normalization constant, and, :math:`c` the contraction coefficients. 70 | 71 | Reference: Molecular Electronic-Structure Theory, https://onlinelibrary.wiley.com/doi/book/10.1002/9781119019572 72 | 73 | Args: 74 | center1: Center of first shell. 75 | center2: Center of second shell. 76 | exponents1: Exponents of primitives in first shell. 77 | exponents2: Exponents of primitives in second shell. 78 | contra_coeff1: Contraction coefficients of primitives in first shell. 79 | contra_coeff2: Contraction coefficients of primitives in second shell. 80 | norm1: Normalization constant of basis-functions in first shell. 81 | norm2: Normalization constant of basis-functions in second shell. 82 | angular_moments1: Cartesian angular moments of basis-functions in first shell. 83 | angular_moments2: Cartesian angular moments of basis-functions in second shell. 84 | atom_coordinates: Coordinates of atoms in the molecule. 85 | atom_charges: Charges of the atoms in the molecule. 86 | 87 | Returns: 88 | Nuclear-attraction integral between two shells. 89 | """ 90 | number_primitives1 = len(exponents1) 91 | number_primitives2 = len(exponents2) 92 | number_bf1 = len(angular_moments1) 93 | number_bf2 = len(angular_moments2) 94 | max_ang1 = np.max(angular_moments1) 95 | max_ang2 = np.max(angular_moments2) 96 | V_primitive = np.zeros((number_bf1, number_bf2, number_primitives1, number_primitives2)) 97 | for i in range(number_primitives1): 98 | for j in range(number_primitives2): 99 | E_x = expansion_coefficients( 100 | center1[0], center2[0], exponents1[i], exponents2[j], max_ang1, max_ang2 101 | ) 102 | E_y = expansion_coefficients( 103 | center1[1], center2[1], exponents1[i], exponents2[j], max_ang1, max_ang2 104 | ) 105 | E_z = expansion_coefficients( 106 | center1[2], center2[2], exponents1[i], exponents2[j], max_ang1, max_ang2 107 | ) 108 | p = exponents1[i] + exponents2[j] 109 | P = (exponents1[i] * center1 + exponents2[j] * center2) / p 110 | for atom_coordinate, charge in zip(atom_coordinates, atom_charges): 111 | R = hermite_coulomb_integral( 112 | max_ang1 + max_ang2, 113 | max_ang1 + max_ang2, 114 | max_ang1 + max_ang2, 115 | exponents1[i] + exponents2[j], 116 | P - atom_coordinate, 117 | ) 118 | for bf_i, (x1, y1, z1) in enumerate(angular_moments1): 119 | temp = norm1[bf_i, i] * charge 120 | for bf_j, (x2, y2, z2) in enumerate(angular_moments2): 121 | V_primitive[bf_i, bf_j, i, j] += ( 122 | temp 123 | * norm2[bf_j, j] 124 | * np.einsum( 125 | "t,u,v,tuv->", 126 | E_x[x1, x2, : x1 + x2 + 1], 127 | E_y[y1, y2, : y1 + y2 + 1], 128 | E_z[z1, z2, : z1 + z2 + 1], 129 | R[: x1 + x2 + 1, : y1 + y2 + 1, : z1 + z2 + 1], 130 | ) 131 | ) 132 | V_primitive[:, :, i, j] *= -2 * np.pi / p 133 | 134 | V_slice = np.einsum("i,j,klij->kl", contra_coeff1, contra_coeff2, V_primitive) 135 | return V_slice 136 | -------------------------------------------------------------------------------- /slowquant/molecule/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikkjellgren/SlowQuant/fbde08b7dd02a5406930e393039c13657bfc3213/slowquant/molecule/__init__.py -------------------------------------------------------------------------------- /slowquant/molecule/basis_reader.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | 5 | 6 | def read_basis(atom_name: str, basis_set: str) -> tuple[np.ndarray, np.ndarray, np.ndarray]: 7 | """Read basis set from file. 8 | 9 | Args: 10 | atom_name: Name of atom. 11 | basis_set: Name of basisset 12 | 13 | Returns: 14 | Basisset information, exponents, contraction coefficients and angular moments. 15 | """ 16 | this_file_location = os.path.dirname(os.path.abspath(__file__)) 17 | basis_set = basis_set.replace("*", "_st_") 18 | if len(atom_name) == 1: 19 | atom_name = f"{atom_name} " 20 | with open(f"{this_file_location}/basisset/{basis_set.lower()}.basis", "r", encoding="UTF-8") as basisfile: 21 | for line in basisfile: 22 | if line[0:2] == atom_name.lower(): 23 | # Found the correct atom. 24 | break 25 | else: 26 | raise ValueError( 27 | "Could not find request atom in basisset.\nBasisset: {basisset}\nAtom: {atom_name}" 28 | ) 29 | exponents = [] 30 | coefficients = [] 31 | angular_moments = [] 32 | number_primitives = -1 33 | for line in basisfile: 34 | if line[0] == "*": 35 | continue 36 | if line[0] != " ": 37 | # Found new atom in file. 38 | break 39 | if "s" in line or "p" in line or "d" in line or "f" in line: 40 | angular = line.split()[1] 41 | number_primitives = int(line.split()[0]) 42 | exponents_primitive: list[float] = [] 43 | coefficients_primitive: list[float] = [] 44 | continue 45 | if number_primitives > 0: 46 | exponents_primitive.append(float(line.split()[0].replace("D", "e"))) 47 | coefficients_primitive.append(float(line.split()[1].replace("D", "e"))) 48 | number_primitives -= 1 49 | if number_primitives == 0: 50 | if angular == "s": 51 | angulars = [[0, 0, 0]] 52 | elif angular == "p": 53 | angulars = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] 54 | elif angular == "d": 55 | angulars = [[2, 0, 0], [1, 1, 0], [1, 0, 1], [0, 2, 0], [0, 1, 1], [0, 0, 2]] 56 | elif angular == "f": 57 | angulars = [ 58 | [3, 0, 0], 59 | [2, 1, 0], 60 | [2, 0, 1], 61 | [1, 2, 0], 62 | [1, 1, 1], 63 | [1, 0, 2], 64 | [0, 3, 0], 65 | [0, 2, 1], 66 | [0, 1, 2], 67 | [0, 0, 3], 68 | ] 69 | else: 70 | raise ValueError(f"None implemented angular moment: {angular}") 71 | exponents.append(np.array(exponents_primitive)) 72 | coefficients.append(np.array(coefficients_primitive)) 73 | angular_moments.append(np.array(angulars)) 74 | number_primitives = -1 75 | return exponents, coefficients, angular_moments 76 | -------------------------------------------------------------------------------- /slowquant/molecule/basisset/4-31g.basis: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # Basis Set Exchange 3 | # Version v0.9 4 | # https://www.basissetexchange.org 5 | #---------------------------------------------------------------------- 6 | # Basis set: 4-31G 7 | # Description: 4-31G valence double-zeta basis set 8 | # Role: orbital 9 | # Version: 1 (Data from Gaussian 09) 10 | #---------------------------------------------------------------------- 11 | 12 | 13 | $basis 14 | * 15 | h 4-31G 16 | * 17 | 3 s 18 | 0.1873113696D+02 0.3349460434D-01 19 | 0.2825394365D+01 0.2347269535D+00 20 | 0.6401216923D+00 0.8137573261D+00 21 | 1 s 22 | 0.1612777588D+00 1.0000000 23 | * 24 | he 4-31G 25 | * 26 | 3 s 27 | 0.3842163400D+02 0.4013973935D-01 28 | 0.5778030000D+01 0.2612460970D+00 29 | 0.1241774000D+01 0.7931846246D+00 30 | 1 s 31 | 0.2979640000D+00 1.0000000 32 | * 33 | b 4-31G 34 | * 35 | 4 s 36 | 0.3307528520D+03 0.1799417960D-01 37 | 0.4984386500D+02 0.1246937000D+00 38 | 0.1111705350D+02 0.4343353750D+00 39 | 0.2922724310D+01 0.5609793740D+00 40 | 3 s 41 | 0.5681264621D+01 -0.1303870779D+00 42 | 0.1454404593D+01 -0.2514343898D+00 43 | 0.4283785757D+00 0.1205129199D+01 44 | 1 s 45 | 0.1442191733D+00 0.1000000000D+01 46 | 3 p 47 | 0.5681264621D+01 0.6374292252D-01 48 | 0.1454404593D+01 0.2761330531D+00 49 | 0.4283785757D+00 0.7773865962D+00 50 | 1 p 51 | 0.1442191733D+00 0.1000000000D+01 52 | * 53 | c 4-31G 54 | * 55 | 4 s 56 | 0.4869669280D+03 0.1772582290D-01 57 | 0.7337109420D+02 0.1234778670D+00 58 | 0.1641345790D+02 0.4338754000D+00 59 | 0.4344983560D+01 0.5615041970D+00 60 | 3 s 61 | 0.8673525310D+01 -0.1213837487D+00 62 | 0.2096619260D+01 -0.2273384975D+00 63 | 0.6046513290D+00 0.1185173917D+01 64 | 1 s 65 | 0.1835578298D+00 0.1000000000D+01 66 | 3 p 67 | 0.8673525310D+01 0.6354538411D-01 68 | 0.2096619260D+01 0.2982677571D+00 69 | 0.6046513290D+00 0.7621032281D+00 70 | 1 p 71 | 0.1835578298D+00 0.1000000000D+01 72 | * 73 | n 4-31G 74 | * 75 | 4 s 76 | 0.6712795030D+03 0.1759825111D-01 77 | 0.1012016620D+03 0.1228462411D+00 78 | 0.2269996590D+02 0.4337821414D+00 79 | 0.6040609000D+01 0.5614182175D+00 80 | 3 s 81 | 0.1239359972D+02 -0.1174892991D+00 82 | 0.2922382831D+01 -0.2139940162D+00 83 | 0.8325280768D+00 0.1174502111D+01 84 | 1 s 85 | 0.2259641730D+00 0.1000000000D+01 86 | 3 p 87 | 0.1239359972D+02 0.6402034433D-01 88 | 0.2922382831D+01 0.3112025551D+00 89 | 0.8325280768D+00 0.7527482393D+00 90 | 1 p 91 | 0.2259641730D+00 0.1000000000D+01 92 | * 93 | o 4-31G 94 | * 95 | 4 s 96 | 0.8832728600D+03 0.1755062799D-01 97 | 0.1331292800D+03 0.1228292230D+00 98 | 0.2990640790D+02 0.4348835838D+00 99 | 0.7978677160D+01 0.5600108038D+00 100 | 3 s 101 | 0.1619444664D+02 -0.1134010029D+00 102 | 0.3780086022D+01 -0.1772864659D+00 103 | 0.1070983575D+01 0.1150407929D+01 104 | 1 s 105 | 0.2838798407D+00 0.1000000000D+01 106 | 3 p 107 | 0.1619444664D+02 0.6854527471D-01 108 | 0.3780086022D+01 0.3312254350D+00 109 | 0.1070983575D+01 0.7346078781D+00 110 | 1 p 111 | 0.2838798407D+00 0.1000000000D+01 112 | * 113 | f 4-31G 114 | * 115 | 4 s 116 | 0.1126162690D+04 0.1747576090D-01 117 | 0.1697431570D+03 0.1225230890D+00 118 | 0.3818151120D+02 0.4349985020D+00 119 | 0.1021203590D+02 0.5598121670D+00 120 | 3 s 121 | 0.2149536670D+02 -0.1110570795D+00 122 | 0.4989777570D+01 -0.1683221018D+00 123 | 0.1403573860D+01 0.1143625555D+01 124 | 1 s 125 | 0.3730318350D+00 0.1000000000D+01 126 | 3 p 127 | 0.2149536670D+02 0.6988875080D-01 128 | 0.4989777570D+01 0.3393875100D+00 129 | 0.1403573860D+01 0.7279589810D+00 130 | 1 p 131 | 0.3730318350D+00 0.1000000000D+01 132 | * 133 | ne 4-31G 134 | * 135 | 4 s 136 | 0.1397932080D+04 0.1742380540D-01 137 | 0.2107697810D+03 0.1222727450D+00 138 | 0.4746725690D+02 0.4350142320D+00 139 | 0.1272262630D+02 0.5597146420D+00 140 | 3 s 141 | 0.2721303320D+02 -0.1096094387D+00 142 | 0.6294134350D+01 -0.1641248895D+00 143 | 0.1760051250D+01 0.1140151586D+01 144 | 1 s 145 | 0.4618669920D+00 0.1000000000D+01 146 | 3 p 147 | 0.2721303320D+02 0.7044030668D-01 148 | 0.6294134350D+01 0.3439930469D+00 149 | 0.1760051250D+01 0.7245149598D+00 150 | 1 p 151 | 0.4618669920D+00 0.1000000000D+01 152 | * 153 | p 4-31G 154 | * 155 | 4 s 156 | 0.3018671780D+04 0.1852131372D-01 157 | 0.4551271210D+03 0.1299048642D+00 158 | 0.1023147300D+03 0.4551002886D+00 159 | 0.2761784730D+02 0.5331318617D+00 160 | 4 s 161 | 0.1144294010D+03 -0.2475029613D-01 162 | 0.2658229590D+02 -0.1350924601D+00 163 | 0.7871888900D+01 0.2277360802D+00 164 | 0.2487857250D+01 0.8755931169D+00 165 | 3 s 166 | 0.5075061900D+02 -0.4511922338D-01 167 | 0.1672862420D+01 -0.8504729971D+00 168 | 0.6210974120D+00 0.1596285863D+01 169 | 1 s 170 | 0.1670160070D+00 0.1000000000D+01 171 | 4 p 172 | 0.1144294010D+03 0.2741400255D-01 173 | 0.2658229590D+02 0.1690791423D+00 174 | 0.7871888900D+01 0.4691020899D+00 175 | 0.2487857250D+01 0.5181530600D+00 176 | 3 p 177 | 0.5075061900D+02 0.3779071215D-02 178 | 0.1672862420D+01 -0.4634384093D-01 179 | 0.6210974120D+00 0.1033944300D+01 180 | 1 p 181 | 0.1670160070D+00 0.1000000000D+01 182 | * 183 | s 4-31G 184 | * 185 | 4 s 186 | 0.3442124410D+04 0.1849212362D-01 187 | 0.5189131000D+03 0.1298220221D+00 188 | 0.1166909030D+03 0.4550417874D+00 189 | 0.3157164720D+02 0.5330083565D+00 190 | 4 s 191 | 0.1274405760D+03 -0.2726461062D-01 192 | 0.2974766730D+02 -0.1424834151D+00 193 | 0.8834664280D+01 0.2597043522D+00 194 | 0.2817389820D+01 0.8525472955D+00 195 | 3 s 196 | 0.3729185370D+01 -0.2775315250D+00 197 | 0.1406770170D+01 -0.4576434583D+00 198 | 0.5481099690D+00 0.1431684280D+01 199 | 1 s 200 | 0.1703809050D+00 0.1000000000D+01 201 | 4 p 202 | 0.1274405760D+03 0.2915199954D-01 203 | 0.2974766730D+02 0.1779596763D+00 204 | 0.8834664280D+01 0.4836237127D+00 205 | 0.2817389820D+01 0.4942553027D+00 206 | 3 p 207 | 0.3729185370D+01 -0.3375092634D-01 208 | 0.1406770170D+01 0.1457110452D+00 209 | 0.5481099690D+00 0.8982887442D+00 210 | 1 p 211 | 0.1703809050D+00 0.1000000000D+01 212 | * 213 | cl 4-31G 214 | * 215 | 4 s 216 | 0.3910302690D+04 0.1837943112D-01 217 | 0.5895518070D+03 0.1291401232D+00 218 | 0.1325939240D+03 0.4540448906D+00 219 | 0.3590354250D+02 0.5344394367D+00 220 | 4 s 221 | 0.1477653530D+03 -0.2674332303D-01 222 | 0.3450607530D+02 -0.1446911822D+00 223 | 0.1028647150D+02 0.2517035693D+00 224 | 0.3311147380D+01 0.8598203819D+00 225 | 3 s 226 | 0.4280284910D+01 -0.2703962754D+00 227 | 0.1641016670D+01 -0.3416297195D+00 228 | 0.6144785030D+00 0.1350024482D+01 229 | 1 s 230 | 0.1956594110D+00 0.1000000000D+01 231 | 4 p 232 | 0.1477653530D+03 0.2886446881D-01 233 | 0.3450607530D+02 0.1779646701D+00 234 | 0.1028647150D+02 0.4869998072D+00 235 | 0.3311147380D+01 0.4890184502D+00 236 | 3 p 237 | 0.4280284910D+01 -0.3670288514D-01 238 | 0.1641016670D+01 0.1918492422D+00 239 | 0.6144785030D+00 0.8643376818D+00 240 | 1 p 241 | 0.1956594110D+00 0.1000000000D+01 242 | * 243 | $end 244 | -------------------------------------------------------------------------------- /slowquant/molecule/basisset/dz.basis: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # Basis Set Exchange 3 | # Version v0.9 4 | # https://www.basissetexchange.org 5 | #---------------------------------------------------------------------- 6 | # Basis set: DZ (Dunning-Hay) 7 | # Description: DZ Double Zeta: 2 Functions/AO 8 | # Role: orbital 9 | # Version: 0 (Data from the original Basis Set Exchange) 10 | #---------------------------------------------------------------------- 11 | 12 | 13 | $basis 14 | * 15 | h DZ (Dunning-Hay) 16 | * 17 | 3 s 18 | 19.2406 0.032828 19 | 2.89920 0.231208 20 | 0.65340 0.817238 21 | 1 s 22 | 0.17760 1.000000 23 | * 24 | li DZ (Dunning-Hay) 25 | * 26 | 6 s 27 | 921.30 0.001367 28 | 138.70 0.010425 29 | 31.940 0.049859 30 | 9.3530 0.160701 31 | 3.1580 0.344604 32 | 1.1570 0.425197 33 | 1 s 34 | 0.44460 1.000000 35 | 1 s 36 | 0.07666 1.000000 37 | 1 s 38 | 0.028640 1.000000 39 | 3 p 40 | 1.488000 0.038770 41 | 0.266700 0.236257 42 | 0.072010 0.830448 43 | 1 p 44 | 0.023700 1.000000 45 | * 46 | b DZ (Dunning-Hay) 47 | * 48 | 6 s 49 | 2788.41 0.002122 50 | 419.039 0.016171 51 | 96.4683 0.078356 52 | 28.0694 0.263250 53 | 9.37600 0.596729 54 | 1.30570 0.230397 55 | 1 s 56 | 3.40620 1.000000 57 | 1 s 58 | 0.3245 1.000000 59 | 1 s 60 | 0.1022 1.000000 61 | 4 p 62 | 11.3413 0.017987 63 | 2.43600 0.110339 64 | 0.68360 0.383111 65 | 0.21340 0.647860 66 | 1 p 67 | 0.07010 1.000000 68 | * 69 | c DZ (Dunning-Hay) 70 | * 71 | 6 s 72 | 4232.61 0.002029 73 | 634.882 0.015535 74 | 146.097 0.075411 75 | 42.4974 0.257121 76 | 14.1892 0.596555 77 | 1.96660 0.242517 78 | 1 s 79 | 5.14770 1.000000 80 | 1 s 81 | 0.4962 1.000000 82 | 1 s 83 | 0.1533 1.000000 84 | 4 p 85 | 18.1557 0.018534 86 | 3.98640 0.115442 87 | 1.14290 0.386206 88 | 0.35940 0.640089 89 | 1 p 90 | 0.11460 1.000000 91 | * 92 | n DZ (Dunning-Hay) 93 | * 94 | 6 s 95 | 5909.44 0.002004 96 | 887.451 0.015310 97 | 204.749 0.074293 98 | 59.8376 0.253364 99 | 19.9981 0.600576 100 | 2.6860 0.245111 101 | 1 s 102 | 7.1927 1.000000 103 | 1 s 104 | 0.7000 1.000000 105 | 1 s 106 | 0.2133 1.000000 107 | 4 p 108 | 26.786 0.018257 109 | 5.9564 0.116407 110 | 1.7074 0.390111 111 | 0.5314 0.637221 112 | 1 p 113 | 0.1654 1.000000 114 | * 115 | o DZ (Dunning-Hay) 116 | * 117 | 6 s 118 | 7816.54 0.002031 119 | 1175.82 0.015436 120 | 273.188 0.073771 121 | 81.1696 0.247606 122 | 27.1836 0.611832 123 | 3.41360 0.241205 124 | 1 s 125 | 9.5322 1.000000 126 | 1 s 127 | 0.9398 1.000000 128 | 1 s 129 | 0.2846 1.000000 130 | 4 p 131 | 35.1832 0.019580 132 | 7.9040 0.124189 133 | 2.3051 0.394727 134 | 0.7171 0.627375 135 | 1 p 136 | 0.2137 1.000000 137 | * 138 | f DZ (Dunning-Hay) 139 | * 140 | 6 s 141 | 9994.79 0.002017 142 | 1506.03 0.015295 143 | 350.269 0.073110 144 | 104.053 0.246420 145 | 34.8432 0.612593 146 | 4.36880 0.242489 147 | 1 s 148 | 12.2164 1.000000 149 | 1 s 150 | 1.2078 1.000000 151 | 1 s 152 | 0.3634 1.000000 153 | 4 p 154 | 44.3555 0.020868 155 | 10.0820 0.130092 156 | 2.99590 0.396219 157 | 0.93830 0.620368 158 | 1 p 159 | 0.2733 1.000000 160 | * 161 | ne DZ (Dunning-Hay) 162 | * 163 | 6 s 164 | 12100. 0.001200 165 | 1821.0 0.009092 166 | 432.80 0.041305 167 | 132.50 0.137867 168 | 43.770 0.362433 169 | 5.1270 0.130035 170 | 1 s 171 | 14.910 1.000000 172 | 1 s 173 | 1.4910 1.000000 174 | 1 s 175 | 0.4468 1.000000 176 | 4 p 177 | 56.450 0.020875 178 | 12.920 0.130032 179 | 3.8650 0.395679 180 | 1.2030 0.621450 181 | 1 p 182 | 0.3444 1.000000 183 | * 184 | al DZ (Dunning-Hay) 185 | * 186 | 5 s 187 | 23490.00 0.002509 188 | 3548.000 0.018986 189 | 823.5000 0.092914 190 | 237.7000 0.335935 191 | 78.60000 0.647391 192 | 3 s 193 | 78.60000 0.111937 194 | 29.05000 0.655976 195 | 11.62000 0.283349 196 | 1 s 197 | 3.465000 1.000000 198 | 1 s 199 | 1.233000 1.000000 200 | 1 s 201 | 0.201800 1.000000 202 | 1 s 203 | 0.078050 1.000000 204 | 4 p 205 | 141.5000 0.017882 206 | 33.22000 0.120375 207 | 10.39000 0.411580 208 | 3.593000 0.595353 209 | 2 p 210 | 3.593000 0.211758 211 | 1.242000 0.837795 212 | 1 p 213 | 0.304000 1.000000 214 | 1 p 215 | 0.076290 1.000000 216 | * 217 | si DZ (Dunning-Hay) 218 | * 219 | 5 s 220 | 26740.00 0.002583 221 | 4076.000 0.019237 222 | 953.3000 0.093843 223 | 274.6000 0.341235 224 | 90.68000 0.641675 225 | 3 s 226 | 90.68000 0.121439 227 | 33.53000 0.653143 228 | 13.46000 0.277624 229 | 1 s 230 | 4.051000 1.000000 231 | 1 s 232 | 1.484000 1.000000 233 | 1 s 234 | 0.270400 1.000000 235 | 1 s 236 | 0.099320 1.000000 237 | 4 p 238 | 163.7000 0.011498 239 | 38.35000 0.077726 240 | 12.02000 0.263595 241 | 4.185000 0.758269 242 | 2 p 243 | 4.185000 -1.173045 244 | 1.483000 1.438335 245 | 1 p 246 | 0.335000 1.000000 247 | 1 p 248 | 0.096990 1.000000 249 | * 250 | p DZ (Dunning-Hay) 251 | * 252 | 5 s 253 | 30630.00 0.002619 254 | 4684.000 0.019479 255 | 1094.000 0.095207 256 | 315.3000 0.345742 257 | 104.1000 0.636288 258 | 3 s 259 | 104.1000 0.130706 260 | 38.42000 0.650274 261 | 15.45000 0.272308 262 | 1 s 263 | 4.656000 1.000000 264 | 1 s 265 | 1.759000 1.000000 266 | 1 s 267 | 0.340900 1.000000 268 | 1 s 269 | 0.123800 1.000000 270 | 4 p 271 | 187.7000 0.013158 272 | 43.63000 0.090494 273 | 13.60000 0.305054 274 | 4.766000 0.713579 275 | 2 p 276 | 4.766000 -0.792573 277 | 1.743000 1.429987 278 | 1 p 279 | 0.419200 1.000000 280 | 1 p 281 | 0.124500 1.000000 282 | * 283 | s DZ (Dunning-Hay) 284 | * 285 | 5 s 286 | 35710.00 0.002565 287 | 5397.000 0.019405 288 | 1250.000 0.095595 289 | 359.9000 0.345793 290 | 119.2000 0.635794 291 | 3 s 292 | 119.2000 0.130096 293 | 43.98000 0.651301 294 | 17.63000 0.271955 295 | 1 s 296 | 5.420000 1.000000 297 | 1 s 298 | 2.074000 1.000000 299 | 1 s 300 | 0.424600 1.000000 301 | 1 s 302 | 0.151900 1.000000 303 | 4 p 304 | 212.9000 0.014091 305 | 49.60000 0.096685 306 | 15.52000 0.323874 307 | 5.476000 0.691756 308 | 2 p 309 | 5.476000 -0.626737 310 | 2.044000 1.377051 311 | 1 p 312 | 0.521800 1.000000 313 | 1 p 314 | 0.150600 1.000000 315 | * 316 | cl DZ (Dunning-Hay) 317 | * 318 | 5 s 319 | 40850.00 0.002532 320 | 6179.000 0.019207 321 | 1425.000 0.095257 322 | 409.2000 0.345589 323 | 135.5000 0.636401 324 | 3 s 325 | 135.5000 0.120956 326 | 50.13000 0.648511 327 | 20.21000 0.275487 328 | 1 s 329 | 6.283000 1.000000 330 | 1 s 331 | 2.460000 1.000000 332 | 1 s 333 | 0.527100 1.000000 334 | 1 s 335 | 0.188400 1.000000 336 | 4 p 337 | 240.8000 0.014595 338 | 56.56000 0.099047 339 | 17.85000 0.330562 340 | 6.350000 0.682874 341 | 2 p 342 | 6.350000 -0.561785 343 | 2.403000 1.351901 344 | 1 p 345 | 0.641000 1.000000 346 | 1 p 347 | 0.183800 1.000000 348 | * 349 | $end 350 | -------------------------------------------------------------------------------- /slowquant/molecule/basisset/dzp.basis: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # Basis Set Exchange 3 | # Version v0.9 4 | # https://www.basissetexchange.org 5 | #---------------------------------------------------------------------- 6 | # Basis set: DZP (Dunning-Hay) 7 | # Description: DZP Double Zeta + Polarization on All Atoms 8 | # Role: orbital 9 | # Version: 0 (Data from the original Basis Set Exchange) 10 | #---------------------------------------------------------------------- 11 | 12 | 13 | $basis 14 | * 15 | h DZP (Dunning-Hay) 16 | * 17 | 3 s 18 | 19.2406 0.032828 19 | 2.89920 0.231208 20 | 0.65340 0.817238 21 | 1 s 22 | 0.17760 1.000000 23 | 1 p 24 | 1.000000 1.000000 25 | * 26 | li DZP (Dunning-Hay) 27 | * 28 | 6 s 29 | 921.30 0.001367 30 | 138.70 0.010425 31 | 31.940 0.049859 32 | 9.3530 0.160701 33 | 3.1580 0.344604 34 | 1.1570 0.425197 35 | 1 s 36 | 0.44460 1.000000 37 | 1 s 38 | 0.07666 1.000000 39 | 1 s 40 | 0.028640 1.000000 41 | 3 p 42 | 1.488000 0.038770 43 | 0.266700 0.236257 44 | 0.072010 0.830448 45 | 1 p 46 | 0.023700 1.000000 47 | 1 d 48 | 0.200000 1.000000 49 | * 50 | b DZP (Dunning-Hay) 51 | * 52 | 6 s 53 | 2788.41 0.002122 54 | 419.039 0.016171 55 | 96.4683 0.078356 56 | 28.0694 0.263250 57 | 9.37600 0.596729 58 | 1.30570 0.230397 59 | 1 s 60 | 3.40620 1.000000 61 | 1 s 62 | 0.3245 1.000000 63 | 1 s 64 | 0.1022 1.000000 65 | 4 p 66 | 11.3413 0.017987 67 | 2.43600 0.110339 68 | 0.68360 0.383111 69 | 0.21340 0.647860 70 | 1 p 71 | 0.07010 1.000000 72 | 1 d 73 | 0.700000 1.000000 74 | * 75 | c DZP (Dunning-Hay) 76 | * 77 | 6 s 78 | 4232.61 0.002029 79 | 634.882 0.015535 80 | 146.097 0.075411 81 | 42.4974 0.257121 82 | 14.1892 0.596555 83 | 1.96660 0.242517 84 | 1 s 85 | 5.14770 1.000000 86 | 1 s 87 | 0.4962 1.000000 88 | 1 s 89 | 0.1533 1.000000 90 | 4 p 91 | 18.1557 0.018534 92 | 3.98640 0.115442 93 | 1.14290 0.386206 94 | 0.35940 0.640089 95 | 1 p 96 | 0.11460 1.000000 97 | 1 d 98 | 0.750000 1.000000 99 | * 100 | n DZP (Dunning-Hay) 101 | * 102 | 6 s 103 | 5909.44 0.002004 104 | 887.451 0.015310 105 | 204.749 0.074293 106 | 59.8376 0.253364 107 | 19.9981 0.600576 108 | 2.6860 0.245111 109 | 1 s 110 | 7.1927 1.000000 111 | 1 s 112 | 0.7000 1.000000 113 | 1 s 114 | 0.2133 1.000000 115 | 4 p 116 | 26.786 0.018257 117 | 5.9564 0.116407 118 | 1.7074 0.390111 119 | 0.5314 0.637221 120 | 1 p 121 | 0.1654 1.000000 122 | 1 d 123 | 0.800000 1.000000 124 | * 125 | o DZP (Dunning-Hay) 126 | * 127 | 6 s 128 | 7816.54 0.002031 129 | 1175.82 0.015436 130 | 273.188 0.073771 131 | 81.1696 0.247606 132 | 27.1836 0.611832 133 | 3.41360 0.241205 134 | 1 s 135 | 9.5322 1.000000 136 | 1 s 137 | 0.9398 1.000000 138 | 1 s 139 | 0.2846 1.000000 140 | 4 p 141 | 35.1832 0.019580 142 | 7.9040 0.124189 143 | 2.3051 0.394727 144 | 0.7171 0.627375 145 | 1 p 146 | 0.2137 1.000000 147 | 1 d 148 | 0.850000 1.000000 149 | * 150 | f DZP (Dunning-Hay) 151 | * 152 | 6 s 153 | 9994.79 0.002017 154 | 1506.03 0.015295 155 | 350.269 0.073110 156 | 104.053 0.246420 157 | 34.8432 0.612593 158 | 4.36880 0.242489 159 | 1 s 160 | 12.2164 1.000000 161 | 1 s 162 | 1.2078 1.000000 163 | 1 s 164 | 0.3634 1.000000 165 | 4 p 166 | 44.3555 0.020868 167 | 10.0820 0.130092 168 | 2.99590 0.396219 169 | 0.93830 0.620368 170 | 1 p 171 | 0.2733 1.000000 172 | 1 d 173 | 0.900000 1.000000 174 | * 175 | ne DZP (Dunning-Hay) 176 | * 177 | 6 s 178 | 12100. 0.001200 179 | 1821.0 0.009092 180 | 432.80 0.041305 181 | 132.50 0.137867 182 | 43.770 0.362433 183 | 5.1270 0.130035 184 | 1 s 185 | 14.910 1.000000 186 | 1 s 187 | 1.4910 1.000000 188 | 1 s 189 | 0.4468 1.000000 190 | 4 p 191 | 56.450 0.020875 192 | 12.920 0.130032 193 | 3.8650 0.395679 194 | 1.2030 0.621450 195 | 1 p 196 | 0.3444 1.000000 197 | 1 d 198 | 0.950000 1.000000 199 | * 200 | al DZP (Dunning-Hay) 201 | * 202 | 5 s 203 | 23490.00 0.002509 204 | 3548.000 0.018986 205 | 823.5000 0.092914 206 | 237.7000 0.335935 207 | 78.60000 0.647391 208 | 3 s 209 | 78.60000 0.111937 210 | 29.05000 0.655976 211 | 11.62000 0.283349 212 | 1 s 213 | 3.465000 1.000000 214 | 1 s 215 | 1.233000 1.000000 216 | 1 s 217 | 0.201800 1.000000 218 | 1 s 219 | 0.078050 1.000000 220 | 4 p 221 | 141.5000 0.017882 222 | 33.22000 0.120375 223 | 10.39000 0.411580 224 | 3.593000 0.595353 225 | 2 p 226 | 3.593000 0.211758 227 | 1.242000 0.837795 228 | 1 p 229 | 0.304000 1.000000 230 | 1 p 231 | 0.076290 1.000000 232 | 1 d 233 | 0.325000 1.000000 234 | * 235 | si DZP (Dunning-Hay) 236 | * 237 | 5 s 238 | 26740.00 0.002583 239 | 4076.000 0.019237 240 | 953.3000 0.093843 241 | 274.6000 0.341235 242 | 90.68000 0.641675 243 | 3 s 244 | 90.68000 0.121439 245 | 33.53000 0.653143 246 | 13.46000 0.277624 247 | 1 s 248 | 4.051000 1.000000 249 | 1 s 250 | 1.484000 1.000000 251 | 1 s 252 | 0.270400 1.000000 253 | 1 s 254 | 0.099320 1.000000 255 | 4 p 256 | 163.7000 0.011498 257 | 38.35000 0.077726 258 | 12.02000 0.263595 259 | 4.185000 0.758269 260 | 2 p 261 | 4.185000 -1.173045 262 | 1.483000 1.438335 263 | 1 p 264 | 0.335000 1.000000 265 | 1 p 266 | 0.096990 1.000000 267 | 1 d 268 | 0.550000 1.000000 269 | * 270 | p DZP (Dunning-Hay) 271 | * 272 | 5 s 273 | 30630.00 0.002619 274 | 4684.000 0.019479 275 | 1094.000 0.095207 276 | 315.3000 0.345742 277 | 104.1000 0.636288 278 | 3 s 279 | 104.1000 0.130706 280 | 38.42000 0.650274 281 | 15.45000 0.272308 282 | 1 s 283 | 4.656000 1.000000 284 | 1 s 285 | 1.759000 1.000000 286 | 1 s 287 | 0.340900 1.000000 288 | 1 s 289 | 0.123800 1.000000 290 | 4 p 291 | 187.7000 0.013158 292 | 43.63000 0.090494 293 | 13.60000 0.305054 294 | 4.766000 0.713579 295 | 2 p 296 | 4.766000 -0.792573 297 | 1.743000 1.429987 298 | 1 p 299 | 0.419200 1.000000 300 | 1 p 301 | 0.124500 1.000000 302 | 1 d 303 | 0.600000 1.000000 304 | * 305 | s DZP (Dunning-Hay) 306 | * 307 | 5 s 308 | 35710.00 0.002565 309 | 5397.000 0.019405 310 | 1250.000 0.095595 311 | 359.9000 0.345793 312 | 119.2000 0.635794 313 | 3 s 314 | 119.2000 0.130096 315 | 43.98000 0.651301 316 | 17.63000 0.271955 317 | 1 s 318 | 5.420000 1.000000 319 | 1 s 320 | 2.074000 1.000000 321 | 1 s 322 | 0.424600 1.000000 323 | 1 s 324 | 0.151900 1.000000 325 | 4 p 326 | 212.9000 0.014091 327 | 49.60000 0.096685 328 | 15.52000 0.323874 329 | 5.476000 0.691756 330 | 2 p 331 | 5.476000 -0.626737 332 | 2.044000 1.377051 333 | 1 p 334 | 0.521800 1.000000 335 | 1 p 336 | 0.150600 1.000000 337 | 1 d 338 | 0.700000 1.000000 339 | * 340 | cl DZP (Dunning-Hay) 341 | * 342 | 5 s 343 | 40850.00 0.002532 344 | 6179.000 0.019207 345 | 1425.000 0.095257 346 | 409.2000 0.345589 347 | 135.5000 0.636401 348 | 3 s 349 | 135.5000 0.120956 350 | 50.13000 0.648511 351 | 20.21000 0.275487 352 | 1 s 353 | 6.283000 1.000000 354 | 1 s 355 | 2.460000 1.000000 356 | 1 s 357 | 0.527100 1.000000 358 | 1 s 359 | 0.188400 1.000000 360 | 4 p 361 | 240.8000 0.014595 362 | 56.56000 0.099047 363 | 17.85000 0.330562 364 | 6.350000 0.682874 365 | 2 p 366 | 6.350000 -0.561785 367 | 2.403000 1.351901 368 | 1 p 369 | 0.641000 1.000000 370 | 1 p 371 | 0.183800 1.000000 372 | 1 d 373 | 0.750000 1.000000 374 | * 375 | $end 376 | -------------------------------------------------------------------------------- /slowquant/molecule/constants.py: -------------------------------------------------------------------------------- 1 | def atom_to_properties(atom_name: str, atom_property: str) -> float: 2 | """Convert atom name into a property. 3 | 4 | Args: 5 | atom_name: Name of element. 6 | atom_property: Property of atom, can be [charge, mass, vdw_radii] 7 | 8 | Returns: 9 | Property of element. 10 | """ 11 | name2number = { 12 | "H": 1, 13 | "He": 2, 14 | "Li": 3, 15 | "Be": 4, 16 | "B": 5, 17 | "C": 6, 18 | "N": 7, 19 | "O": 8, 20 | "F": 9, 21 | "Ne": 10, 22 | "Na": 11, 23 | "Mg": 12, 24 | "Al": 13, 25 | "Si": 14, 26 | "P": 15, 27 | "S": 16, 28 | "Cl": 17, 29 | "Ar": 18, 30 | "K": 19, 31 | "Ca": 20, 32 | "Sc": 21, 33 | "Ti": 22, 34 | "V": 23, 35 | "Cr": 24, 36 | "Mn": 25, 37 | "Fe": 26, 38 | "Co": 27, 39 | "Ni": 28, 40 | "Cu": 29, 41 | "Zn": 30, 42 | "Ga": 31, 43 | "Ge": 32, 44 | "As": 33, 45 | "Se": 34, 46 | "Br": 35, 47 | "Kr": 36, 48 | "Rb": 37, 49 | "Sr": 38, 50 | "Y": 39, 51 | "Zr": 40, 52 | "Nb": 41, 53 | "Mo": 42, 54 | "Tc": 43, 55 | "Ru": 44, 56 | "Rh": 45, 57 | "Pd": 46, 58 | "Ag": 47, 59 | "Cd": 48, 60 | "In": 49, 61 | "Sn": 50, 62 | "Sb": 51, 63 | "Te": 52, 64 | "I": 53, 65 | "Xe": 54, 66 | "Cs": 55, 67 | "Ba": 56, 68 | "La": 57, 69 | "Ce": 58, 70 | "Pr": 59, 71 | "Nd": 60, 72 | "Pm": 61, 73 | "Sm": 62, 74 | "Eu": 63, 75 | "Gd": 64, 76 | "Tb": 65, 77 | "Dy": 66, 78 | "Ho": 67, 79 | "Er": 68, 80 | "Tm": 69, 81 | "Yb": 70, 82 | "Lu": 71, 83 | "Hf": 72, 84 | "Ta": 73, 85 | "W": 74, 86 | "Re": 75, 87 | "Os": 76, 88 | "Ir": 77, 89 | "Pt": 78, 90 | "Au": 79, 91 | "Hg": 80, 92 | "Tl": 81, 93 | "Pb": 82, 94 | "Bi": 83, 95 | "Po": 84, 96 | "At": 85, 97 | "Rn": 86, 98 | "Fr": 87, 99 | "Ra": 88, 100 | "Ac": 89, 101 | "Th": 90, 102 | "Pa": 91, 103 | "U": 92, 104 | "Np": 93, 105 | "Pu": 94, 106 | "Am": 95, 107 | "Cm": 96, 108 | "Bk": 97, 109 | "Cf": 98, 110 | "Es": 99, 111 | "Fm": 100, 112 | "Md": 101, 113 | "No": 102, 114 | "Lr": 103, 115 | "Rf": 104, 116 | "Db": 105, 117 | "Sg": 106, 118 | "Bh": 107, 119 | "Hs": 108, 120 | "Mt": 109, 121 | "Ds": 110, 122 | "Rg": 111, 123 | "Cn": 112, 124 | "Nh": 113, 125 | "Fl": 114, 126 | "Mc": 115, 127 | "Lv": 116, 128 | "Ts": 117, 129 | "Og": 118, 130 | } 131 | 132 | vdw_radii = { 133 | 1: 2.26767118629, 134 | 2: 2.64561638401, 135 | 3: 3.43930129921, 136 | 4: 2.89128076253, 137 | 5: 3.62827389807, 138 | 6: 3.21253418058, 139 | 7: 2.9290752823, 140 | 8: 2.87238350264, 141 | 9: 2.77789720321, 142 | 10: 2.91017802241, 143 | 11: 4.28967799407, 144 | 12: 3.26922596024, 145 | 13: 3.47709581899, 146 | 14: 3.96842457602, 147 | 15: 3.40150677944, 148 | 16: 3.40150677944, 149 | 17: 3.30702048001, 150 | 18: 3.55268485853, 151 | 19: 5.19674646859, 152 | 20: 4.36526703362, 153 | 21: 3.9873218359, 154 | 22: False, 155 | 23: False, 156 | 24: False, 157 | 25: False, 158 | 26: False, 159 | 27: False, 160 | 28: 3.08025336138, 161 | 29: 2.64561638401, 162 | 30: 2.62671912412, 163 | 31: 3.53378759864, 164 | 32: 3.9873218359, 165 | 33: 3.49599307887, 166 | 34: 3.5904793783, 167 | 35: 3.49599307887, 168 | 36: 3.81724649693, 169 | 37: 5.72586974539, 170 | 38: 4.70541771156, 171 | 39: False, 172 | 40: False, 173 | 41: False, 174 | 42: False, 175 | 43: False, 176 | 44: False, 177 | 45: False, 178 | 46: 3.08025336138, 179 | 47: 3.25032870036, 180 | 48: 2.98576706195, 181 | 49: 3.64717115796, 182 | 50: 4.10070539522, 183 | 51: 3.89283553647, 184 | 52: 3.89283553647, 185 | 53: 3.74165745739, 186 | 54: 4.08180813533, 187 | 55: 6.48176014083, 188 | 56: 5.06446564939, 189 | 57: False, 190 | 58: False, 191 | 59: False, 192 | 60: False, 193 | 61: False, 194 | 62: False, 195 | 63: False, 196 | 64: False, 197 | 65: False, 198 | 66: False, 199 | 67: False, 200 | 68: False, 201 | 69: False, 202 | 70: False, 203 | 71: False, 204 | 72: False, 205 | 73: False, 206 | 74: False, 207 | 75: False, 208 | 76: False, 209 | 77: False, 210 | 78: 3.30702048001, 211 | 79: 3.13694514104, 212 | 80: 2.9290752823, 213 | 81: 3.70386293761, 214 | 82: 3.81724649693, 215 | 83: 3.91173279636, 216 | 84: 3.7227601975, 217 | 85: 3.81724649693, 218 | 86: 4.15739717487, 219 | 87: 6.57624644025, 220 | 88: 5.34792454768, 221 | 89: False, 222 | 90: False, 223 | 91: False, 224 | 92: 3.51489033876, 225 | 93: False, 226 | 94: False, 227 | 95: False, 228 | 96: False, 229 | 97: False, 230 | 98: False, 231 | 99: False, 232 | 100: False, 233 | 101: False, 234 | 102: False, 235 | 103: False, 236 | 104: False, 237 | 105: False, 238 | 106: False, 239 | 107: False, 240 | 108: False, 241 | 109: False, 242 | 110: False, 243 | 111: False, 244 | 112: False, 245 | 113: False, 246 | 114: False, 247 | 115: False, 248 | 116: False, 249 | 117: False, 250 | 118: False, 251 | } 252 | 253 | mass = { 254 | "H": 1.008, 255 | "Na": 22.989, 256 | "Sc": 44.955, 257 | "Ga": 69.723, 258 | "Nb": 92.906, 259 | "Sb": 121.76, 260 | "Pm": False, 261 | "Lu": 174.9668, 262 | "Tl": 204.38, 263 | "Pa": 231.035, 264 | "Md": False, 265 | "Rg": False, 266 | "He": 4.002, 267 | "Mg": 24.305, 268 | "Ti": 47.867, 269 | "Ge": 72.63, 270 | "Mo": 95.95, 271 | "Te": 127.6, 272 | "Sm": 150.36, 273 | "Hf": 178.49, 274 | "Pb": 207.2, 275 | "U": 238.028, 276 | "No": False, 277 | "Cn": False, 278 | "Li": 6.94, 279 | "Al": 26.981, 280 | "V": 50.9415, 281 | "As": 74.921, 282 | "Tc": False, 283 | "I": 126.904, 284 | "Eu": 151.964, 285 | "Ta": 180.947, 286 | "Bi": 208.98, 287 | "Np": False, 288 | "Lr": False, 289 | "Nh": False, 290 | "Be": 9.012, 291 | "Si": 28.085, 292 | "Cr": 51.9961, 293 | "Se": 78.971, 294 | "Ru": 101.07, 295 | "Xe": 131.293, 296 | "Gd": 157.25, 297 | "W": 183.84, 298 | "Po": False, 299 | "Pu": False, 300 | "Rf": False, 301 | "Fl": False, 302 | "B": 10.81, 303 | "P": 30.973, 304 | "Mn": 54.938, 305 | "Br": 79.904, 306 | "Rh": 102.905, 307 | "Cs": 132.905, 308 | "Tb": 158.925, 309 | "Re": 186.207, 310 | "At": False, 311 | "Am": False, 312 | "Db": False, 313 | "Mc": False, 314 | "C": 12.011, 315 | "S": 32.06, 316 | "Fe": 55.845, 317 | "Kr": 83.798, 318 | "Pd": 106.42, 319 | "Ba": 137.327, 320 | "Dy": 162.5, 321 | "Os": 190.23, 322 | "Rn": False, 323 | "Cm": False, 324 | "Sg": False, 325 | "Lv": False, 326 | "N": 14.007, 327 | "Cl": 35.45, 328 | "Co": 58.933, 329 | "Rb": 85.4678, 330 | "Ag": 107.8682, 331 | "La": 138.905, 332 | "Ho": 164.93, 333 | "Ir": 192.217, 334 | "Fr": False, 335 | "Bk": False, 336 | "Bh": False, 337 | "Ts": False, 338 | "O": 15.999, 339 | "Ar": 39.948, 340 | "Ni": 58.6934, 341 | "Sr": 87.62, 342 | "Cd": 112.414, 343 | "Ce": 140.116, 344 | "Er": 167.259, 345 | "Pt": 195.084, 346 | "Ra": False, 347 | "Cf": False, 348 | "Hs": False, 349 | "Og": False, 350 | "F": 18.998, 351 | "K": 39.0983, 352 | "Cu": 63.546, 353 | "Y": 88.905, 354 | "In": 114.818, 355 | "Pr": 140.907, 356 | "Tm": 168.934, 357 | "Au": 196.966, 358 | "Ac": False, 359 | "Es": False, 360 | "Mt": False, 361 | "Ne": 20.1797, 362 | "Ca": 40.078, 363 | "Zn": 65.38, 364 | "Zr": 91.224, 365 | "Sn": 118.71, 366 | "Nd": 144.242, 367 | "Yb": 173.045, 368 | "Hg": 200.592, 369 | "Th": 232.0377, 370 | "Fm": False, 371 | "Ds": False, 372 | } 373 | 374 | if atom_property.lower() == "charge": 375 | return name2number[atom_name] 376 | if atom_property.lower() == "mass": 377 | return mass[atom_name] 378 | if atom_property.lower() == "vdw_radii": 379 | return vdw_radii[name2number[atom_name]] 380 | raise ValueError("Request invalid property. Requested property: {atom_property}") 381 | -------------------------------------------------------------------------------- /slowquant/molecule/moleculeclass.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from slowquant.molecule.basis_reader import read_basis 4 | from slowquant.molecule.constants import atom_to_properties 5 | from slowquant.molecule.moleculefunctions import ( 6 | contracted_normalization, 7 | primitive_gauss, 8 | primitive_normalization, 9 | ) 10 | 11 | 12 | class _Molecule: 13 | def __init__(self, molecule_file: str, molecular_charge_: int = 0, distance_unit: str = "bohr") -> None: 14 | """Initialize molecule instance. 15 | 16 | Args: 17 | molecule_file: Filename of file containing molecular coordinates. 18 | molecular_charge_: Total charge of molecule. 19 | distance_unit: Distance unit used coordinate file. 20 | Angstrom or Bohr (default). 21 | Internal representation is Bohr. 22 | """ 23 | self.molecular_charge = molecular_charge_ 24 | self.shells: list[Shell] 25 | self.number_bf = 0 26 | if distance_unit.lower() == "angstrom": 27 | unit_factor = 1.889725989 28 | elif distance_unit.lower() == "au" or distance_unit.lower() == "bohr": 29 | unit_factor = 1.0 30 | else: 31 | raise ValueError( 32 | "distance_unit not valid can be 'angstrom' or 'bohr'. Was given: {distance_unit}" 33 | ) 34 | 35 | if ".xyz" in molecule_file: 36 | with open(molecule_file, "r", encoding="UTF-8") as file: 37 | self.atoms = [] 38 | for i, line in enumerate(file): 39 | if i < 2: 40 | continue 41 | self.atoms.append( 42 | Atom( 43 | line.split()[0], 44 | np.array( 45 | [ 46 | float(line.split()[1]) * unit_factor, 47 | float(line.split()[2]) * unit_factor, 48 | float(line.split()[3]) * unit_factor, 49 | ] 50 | ), 51 | int(atom_to_properties(line.split()[0], "charge")), 52 | atom_to_properties(line.split()[0], "mass"), 53 | ) 54 | ) 55 | elif ";" in molecule_file: 56 | lines = molecule_file.split(";") 57 | self.atoms = [] 58 | for line in lines: 59 | if len(line.strip()) == 0: 60 | # If last line in input got an ";", 61 | # then last line in the reading will be empty. 62 | continue 63 | self.atoms.append( 64 | Atom( 65 | line.split()[0], 66 | np.array( 67 | [ 68 | float(line.split()[1]) * unit_factor, 69 | float(line.split()[2]) * unit_factor, 70 | float(line.split()[3]) * unit_factor, 71 | ] 72 | ), 73 | int(atom_to_properties(line.split()[0], "charge")), 74 | atom_to_properties(line.split()[0], "mass"), 75 | ) 76 | ) 77 | else: 78 | raise ValueError( 79 | "Does only support:\n .xyz files for molecule coordinates.\n A string with the elements and coordinates (; delimited)." 80 | ) 81 | 82 | def _set_basis_set(self, basis_set: str) -> None: 83 | """Set basis set. 84 | 85 | Args: 86 | basis_set: Name of basis set. 87 | """ 88 | self.shells = [] 89 | self.number_bf = 0 90 | for atom in self.atoms: 91 | bfs_exponents, bfs_contraction_coefficients, bfs_angular_moments = read_basis( 92 | atom.atom_name, basis_set 93 | ) 94 | for bf_exponents, bf_contraction_coefficients, bf_angular_moments in zip( 95 | bfs_exponents, bfs_contraction_coefficients, bfs_angular_moments 96 | ): 97 | self.shells.append( 98 | Shell( 99 | atom.coordinate, 100 | bf_exponents, 101 | bf_contraction_coefficients, 102 | bf_angular_moments, 103 | self.number_bf, 104 | atom, 105 | ) 106 | ) 107 | self.number_bf += len(bf_angular_moments) 108 | 109 | @property 110 | def atom_coordinates(self) -> np.ndarray: 111 | """Get atom coordinates. 112 | 113 | Returns: 114 | Atom coordinates. 115 | """ 116 | coords = np.zeros((len(self.atoms), 3)) 117 | for i, atom in enumerate(self.atoms): 118 | coords[i, :] = atom.coordinate 119 | return coords 120 | 121 | @property 122 | def atom_charges(self) -> np.ndarray: 123 | """Get atom charges. 124 | 125 | Returns: 126 | Atom charges. 127 | """ 128 | charges = np.zeros(len(self.atoms)) 129 | for i, atom in enumerate(self.atoms): 130 | charges[i] = atom.nuclear_charge 131 | return charges 132 | 133 | @property 134 | def number_electrons(self) -> int: 135 | """Get number of electrons. 136 | 137 | Returns: 138 | Number of electrons. 139 | """ 140 | n_elec = -self.molecular_charge 141 | for atom in self.atoms: 142 | n_elec += atom.nuclear_charge 143 | return n_elec 144 | 145 | @property 146 | def number_electrons_alpha(self) -> int: 147 | """Get number of alpha electrons. 148 | 149 | Returns: 150 | Number of alpha electrons. 151 | """ 152 | n_elec = -self.molecular_charge 153 | for atom in self.atoms: 154 | n_elec += atom.nuclear_charge 155 | return (self.number_electrons + 1) // 2 156 | 157 | @property 158 | def number_electrons_beta(self) -> int: 159 | """Get number of beta electrons. 160 | 161 | Returns: 162 | Number of beta electrons. 163 | """ 164 | return self.number_electrons // 2 165 | 166 | @property 167 | def nuclear_repulsion(self) -> float: 168 | r"""Get nuclear-nuclear repulsion. 169 | 170 | .. math:: 171 | V_\mathrm{NN} = \sum_{i < j}\frac{Z_i Z_j}{|R_i-R_j|} 172 | 173 | Returns: 174 | Nuclear-nuclear repulsion. 175 | """ 176 | Z = self.atom_charges 177 | R = self.atom_coordinates 178 | V = 0.0 179 | for i, (Z_i, R_i) in enumerate(zip(Z, R)): 180 | for j, (Z_j, R_j) in enumerate(zip(Z, R)): 181 | if i >= j: 182 | continue 183 | V += Z_i * Z_j / np.einsum("k->", (R_i - R_j) ** 2) ** 0.5 184 | return V 185 | 186 | @property 187 | def center_of_mass(self) -> np.ndarray: 188 | r"""Get center of mass. 189 | 190 | Returns: 191 | Center of mass. 192 | """ 193 | masses = np.zeros(len(self.atoms)) 194 | for i, atom in enumerate(self.atoms): 195 | masses[i] = atom.mass 196 | return np.einsum("ij,i->j", self.atom_coordinates, masses) / np.einsum("i->", masses) 197 | 198 | @property 199 | def basis_function_labels(self) -> list[str]: 200 | """Get labels of basis functions. 201 | 202 | Returns: 203 | Labels of basis functions. 204 | """ 205 | bf_labels = [] 206 | for shell in self.shells: 207 | for angular_moment in shell.angular_moments: 208 | bf_labels.append(f"{shell.origin_atom.atom_name} {angular_moment}") 209 | return bf_labels 210 | 211 | @property 212 | def number_shell(self) -> int: 213 | """Get number of shells. 214 | 215 | Returns: 216 | Number of shells. 217 | """ 218 | return len(self.shells) 219 | 220 | def get_basis_function_amplitude(self, points: np.ndarray) -> np.ndarray: 221 | """Compute basis function amplitudes in a set of points. 222 | 223 | Args: 224 | points: Points in which the basis function amplitudes are evaulated. 225 | 226 | Returns: 227 | Basis function amplitudes in a set of points. 228 | """ 229 | bf_amplitudes = np.zeros((len(points), self.number_bf)) 230 | for shell in self.shells: 231 | x, y, z = shell.center 232 | for bf_i in range(len(shell.angular_moments)): 233 | bf_idx = shell.bf_idx[bf_i] 234 | ang_x, ang_y, ang_z = shell.angular_moments[bf_i] 235 | for prim_i in range(len(shell.contraction_coefficients)): 236 | exponent = shell.exponents[prim_i] 237 | coeff = shell.contraction_coefficients[prim_i] 238 | norm = shell.normalization[bf_i, prim_i] 239 | for k, point in enumerate(points): 240 | px, py, pz = point 241 | bf_amplitudes[k, bf_idx] += ( 242 | norm * coeff * primitive_gauss(px, py, pz, x, y, z, exponent, ang_x, ang_y, ang_z) 243 | ) 244 | return bf_amplitudes 245 | 246 | 247 | class Atom: 248 | def __init__( 249 | self, 250 | name: str, 251 | coordinate_: float, 252 | charge: int, 253 | mass_: float, 254 | ) -> None: 255 | """Initialize atom instance. 256 | 257 | Args: 258 | name: Atom name. 259 | coordinate_: Atom coordinate. 260 | charge: Atom nuclear charge. 261 | mass_: Atomic mass. 262 | """ 263 | self.atom_name = name 264 | self.coordinate = coordinate_ 265 | self.nuclear_charge = charge 266 | self.mass = mass_ 267 | 268 | 269 | class Shell: 270 | def __init__( 271 | self, 272 | center_: np.ndarray, 273 | exponents_: np.ndarray, 274 | contraction_coefficients_: np.ndarray, 275 | angular_moments_: np.ndarray, 276 | bf_idx_: int, 277 | origin_atom_: Atom, 278 | ): 279 | """Initialize shell instance. 280 | 281 | Args: 282 | center_: x,y,z-coordinate for shell location. 283 | exponents_: Gaussian exponents. 284 | contraction_coefficients_: Contraction coefficients. 285 | angular_moments_: Angular moments of the form x,y,z. 286 | bf_idx_: Starting index of basis-function in shell. 287 | origin_atom_: Atom which the shell is placed on. 288 | """ 289 | self.center = center_ 290 | self.exponents = exponents_ 291 | self.contraction_coefficients = contraction_coefficients_ 292 | self.normalization = np.zeros((len(angular_moments_), len(exponents_))) 293 | self.origin_atom = origin_atom_ 294 | for bf_i, angular_moment in enumerate(angular_moments_): 295 | for i, exponent in enumerate(exponents_): 296 | self.normalization[bf_i, i] = primitive_normalization(exponent, angular_moment) 297 | self.normalization[bf_i, :] *= contracted_normalization( 298 | exponents_, contraction_coefficients_ * self.normalization[bf_i, :], angular_moment 299 | ) 300 | self.angular_moments = angular_moments_ 301 | self.bf_idx = np.array((range(bf_idx_, bf_idx_ + len(angular_moments_)))) 302 | -------------------------------------------------------------------------------- /slowquant/molecule/moleculefunctions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numba import float64, jit 3 | 4 | 5 | # Is float64. int64 gives overflow. 6 | # Only approximate value as a float is needed. 7 | @jit(float64(float64), nopython=True, cache=True) 8 | def factorial2(number: float) -> float: 9 | r"""Double factorial. 10 | 11 | .. math:: 12 | n!! = \prod^{\lceil n/2\rceil-1}_{k=0}(n-2k) 13 | 14 | Reference: https://en.wikipedia.org/wiki/Double_factorial 15 | 16 | Args: 17 | number: Integer. 18 | 19 | Returns: 20 | Double factorial of number. 21 | """ 22 | out = 1.0 23 | if number > 0: 24 | for i in range(0, int(number + 1) // 2): 25 | out = out * (number - 2 * i) 26 | return out 27 | 28 | 29 | def primitive_normalization(exponent: float, angular_moment: np.ndarray) -> float: 30 | r"""Normalize primitive Gaussian. 31 | 32 | .. math:: 33 | N = \left(\frac{2}{\pi}\right)^{3/4} \frac{2^{l+m+n}\alpha^{(2l+2m+2n+3)/4}}{[(2l-1)!!(2m-1)!!(2n-1)!!]^{1/2}} 34 | 35 | Args: 36 | exponent: Gaussian exponent. 37 | angular_moment: Cartesian angular moment of primitive Gaussian. 38 | 39 | Returns: 40 | Normalization constant for primitive Gaussian. 41 | """ 42 | l, m, n = angular_moment 43 | return ( 44 | (2 / np.pi) ** (3 / 4) 45 | * (2 ** (l + m + n) * exponent ** ((2 * l + 2 * m + 2 * n + 3) / 4)) 46 | / (factorial2(2 * l - 1) * factorial2(2 * m - 1) * factorial2(2 * n - 1)) ** (1 / 2) 47 | ) 48 | 49 | 50 | def contracted_normalization( 51 | exponents: np.ndarray, 52 | coefficients: np.ndarray, 53 | angular_moments: np.ndarray, 54 | ) -> float: 55 | r"""Normalize contracted Gaussian. 56 | 57 | .. math:: 58 | N = \left[ \frac{\pi^{3/2}(2l-1)!!(2m-1)!!(2n-1)!!}{2^{l+m+n}} \sum_{i,j}^n\frac{a_ia_j}{\left(\alpha_i+\alpha_j\right)^{l+m+n+3/2}} \right]^{-1/2} 59 | 60 | With the cartesian angular moments being :math:`l`, :math:`m`, and :math:`n`, and the Gaussian exponent being :math:`\alpha`, and the contraction coefficients being :math:`a`. 61 | 62 | Reference: Fundamentals of Molecular Integrals Evaluation, https://arxiv.org/abs/2007.12057 63 | 64 | Args: 65 | exponents: Gaussian exponents. 66 | coefficients: Contraction coefficients. 67 | angular_moments: Angular moment of Gaussian orbital. 68 | 69 | Returns: 70 | Normalization constant for contracted Gaussian. 71 | """ 72 | normalization_factor = 0 73 | angular_x, angular_y, angular_z = angular_moments 74 | number_primitives = len(exponents) 75 | for i in range(number_primitives): 76 | for j in range(number_primitives): 77 | normalization_factor += ( 78 | coefficients[i] 79 | * coefficients[j] 80 | / (exponents[i] + exponents[j]) ** (angular_x + angular_y + angular_z + 3 / 2) 81 | ) 82 | normalization_factor *= ( 83 | np.pi ** (3 / 2) 84 | * factorial2(2 * angular_x - 1) 85 | * factorial2(2 * angular_y - 1) 86 | * factorial2(2 * angular_z - 1) 87 | ) / (2 ** (angular_x + angular_y + angular_z)) 88 | return normalization_factor ** (-1 / 2) 89 | 90 | 91 | def primitive_gauss( 92 | coord_x: float, 93 | coord_y: float, 94 | coord_z: float, 95 | center_x: float, 96 | center_y: float, 97 | center_z: float, 98 | exponent: float, 99 | ang_x: int, 100 | ang_y: int, 101 | ang_z: int, 102 | ) -> float: 103 | """Primitive Gaussian.""" 104 | return ( 105 | (coord_x - center_x) ** ang_x 106 | * (coord_y - center_y) ** ang_y 107 | * (coord_z - center_z) ** ang_z 108 | * np.exp( 109 | -exponent * ((coord_x - center_x) ** 2 + (coord_y - center_y) ** 2 + (coord_z - center_z) ** 2) 110 | ) 111 | ) 112 | -------------------------------------------------------------------------------- /slowquant/qiskit_interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikkjellgren/SlowQuant/fbde08b7dd02a5406930e393039c13657bfc3213/slowquant/qiskit_interface/__init__.py -------------------------------------------------------------------------------- /slowquant/unitary_coupled_cluster/__init__.py: -------------------------------------------------------------------------------- 1 | """Unitary Coupled Cluster module.""" 2 | -------------------------------------------------------------------------------- /slowquant/unitary_coupled_cluster/linear_response/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erikkjellgren/SlowQuant/fbde08b7dd02a5406930e393039c13657bfc3213/slowquant/unitary_coupled_cluster/linear_response/__init__.py -------------------------------------------------------------------------------- /slowquant/unitary_coupled_cluster/linear_response/lr_baseclass.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | 3 | import numpy as np 4 | import scipy 5 | 6 | from slowquant.unitary_coupled_cluster.ci_spaces import CI_Info 7 | from slowquant.unitary_coupled_cluster.fermionic_operator import FermionicOperator 8 | from slowquant.unitary_coupled_cluster.operators import ( 9 | G3, 10 | G4, 11 | G5, 12 | G6, 13 | Epq, 14 | G1_sa, 15 | G2_1_sa, 16 | G2_2_sa, 17 | hamiltonian_0i_0a, 18 | hamiltonian_1i_1a, 19 | ) 20 | from slowquant.unitary_coupled_cluster.ucc_wavefunction import WaveFunctionUCC 21 | from slowquant.unitary_coupled_cluster.ups_wavefunction import WaveFunctionUPS 22 | from slowquant.unitary_coupled_cluster.util import ( 23 | UccStructure, 24 | UpsStructure, 25 | iterate_t1_sa, 26 | iterate_t2_sa, 27 | iterate_t3, 28 | iterate_t4, 29 | iterate_t5, 30 | iterate_t6, 31 | ) 32 | 33 | 34 | class LinearResponseBaseClass: 35 | index_info: tuple[CI_Info, list[float], UpsStructure] | tuple[CI_Info, list[float], UccStructure] 36 | 37 | def __init__( 38 | self, 39 | wave_function: WaveFunctionUCC | WaveFunctionUPS, 40 | excitations: str, 41 | ) -> None: 42 | """Initialize linear response by calculating the needed matrices. 43 | 44 | Args: 45 | wave_function: Wave function object. 46 | excitations: Which excitation orders to include in response. 47 | """ 48 | self.wf = wave_function 49 | if isinstance(self.wf, WaveFunctionUCC): 50 | self.index_info = ( 51 | self.wf.ci_info, 52 | self.wf.thetas, 53 | self.wf.ucc_layout, 54 | ) 55 | elif isinstance(self.wf, WaveFunctionUPS): 56 | self.index_info = ( 57 | self.wf.ci_info, 58 | self.wf.thetas, 59 | self.wf.ups_layout, 60 | ) 61 | else: 62 | raise ValueError(f"Got incompatible wave function type, {type(self.wf)}") 63 | 64 | self.G_ops: list[FermionicOperator] = [] 65 | self.q_ops: list[FermionicOperator] = [] 66 | excitations = excitations.lower() 67 | 68 | if "s" in excitations: 69 | for a, i, _ in iterate_t1_sa(self.wf.active_occ_idx, self.wf.active_unocc_idx): 70 | self.G_ops.append(G1_sa(i, a)) 71 | if "d" in excitations: 72 | for a, i, b, j, _, op_type in iterate_t2_sa(self.wf.active_occ_idx, self.wf.active_unocc_idx): 73 | if op_type == 1: 74 | self.G_ops.append(G2_1_sa(i, j, a, b)) 75 | elif op_type == 2: 76 | self.G_ops.append(G2_2_sa(i, j, a, b)) 77 | if "t" in excitations: 78 | for a, i, b, j, c, k in iterate_t3(self.wf.active_occ_spin_idx, self.wf.active_unocc_spin_idx): 79 | self.G_ops.append(G3(i, j, k, a, b, c)) 80 | if "q" in excitations: 81 | for a, i, b, j, c, k, d, l in iterate_t4( 82 | self.wf.active_occ_spin_idx, self.wf.active_unocc_spin_idx 83 | ): 84 | self.G_ops.append(G4(i, j, k, l, a, b, c, d)) 85 | if "5" in excitations: 86 | for a, i, b, j, c, k, d, l, e, m in iterate_t5( 87 | self.wf.active_occ_spin_idx, self.wf.active_unocc_spin_idx 88 | ): 89 | self.G_ops.append(G5(i, j, k, l, m, a, b, c, d, e)) 90 | if "6" in excitations: 91 | for a, i, b, j, c, k, d, l, e, m, f, n in iterate_t6( 92 | self.wf.active_occ_spin_idx, self.wf.active_unocc_spin_idx 93 | ): 94 | self.G_ops.append(G6(i, j, k, l, m, n, a, b, c, d, e, f)) 95 | for i, a in self.wf.kappa_no_activeactive_idx: 96 | op = 2 ** (-1 / 2) * Epq(a, i) 97 | self.q_ops.append(op) 98 | 99 | num_parameters = len(self.G_ops) + len(self.q_ops) 100 | self.A = np.zeros((num_parameters, num_parameters)) 101 | self.B = np.zeros((num_parameters, num_parameters)) 102 | self.Sigma = np.zeros((num_parameters, num_parameters)) 103 | self.Delta = np.zeros((num_parameters, num_parameters)) 104 | self.H_1i_1a = hamiltonian_1i_1a( 105 | self.wf.h_mo, 106 | self.wf.g_mo, 107 | self.wf.num_inactive_orbs, 108 | self.wf.num_active_orbs, 109 | self.wf.num_virtual_orbs, 110 | ) 111 | self.H_0i_0a = hamiltonian_0i_0a( 112 | self.wf.h_mo, 113 | self.wf.g_mo, 114 | self.wf.num_inactive_orbs, 115 | self.wf.num_active_orbs, 116 | ) 117 | 118 | def calc_excitation_energies(self) -> None: 119 | """Calculate excitation energies.""" 120 | size = len(self.A) 121 | E2 = np.zeros((size * 2, size * 2)) 122 | E2[:size, :size] = self.A 123 | E2[:size, size:] = self.B 124 | E2[size:, :size] = self.B 125 | E2[size:, size:] = self.A 126 | ( 127 | hess_eigval, 128 | _, 129 | ) = np.linalg.eig(E2) 130 | print(f"Smallest Hessian eigenvalue: {np.min(hess_eigval)}") 131 | if np.abs(np.min(hess_eigval)) < 10**-8: 132 | print("WARNING: Small eigenvalue in Hessian") 133 | elif np.min(hess_eigval) < 0: 134 | raise ValueError("Negative eigenvalue in Hessian.") 135 | 136 | S = np.zeros((size * 2, size * 2)) 137 | S[:size, :size] = self.Sigma 138 | S[:size, size:] = self.Delta 139 | S[size:, :size] = -self.Delta 140 | S[size:, size:] = -self.Sigma 141 | print(f"Smallest diagonal element in the metric: {np.min(np.abs(np.diagonal(self.Sigma)))}") 142 | 143 | self.hessian = E2 144 | self.metric = S 145 | 146 | eigval, eigvec = scipy.linalg.eig(self.hessian, self.metric) 147 | sorting = np.argsort(eigval) 148 | self.excitation_energies = np.real(eigval[sorting][size:]) 149 | self.response_vectors = np.real(eigvec[:, sorting][:, size:]) 150 | self.normed_response_vectors = np.zeros_like(self.response_vectors) 151 | self.num_q = len(self.q_ops) 152 | self.num_G = size - self.num_q 153 | self.Z_q = self.response_vectors[: self.num_q, :] 154 | self.Z_G = self.response_vectors[self.num_q : self.num_q + self.num_G, :] 155 | self.Y_q = self.response_vectors[self.num_q + self.num_G : 2 * self.num_q + self.num_G] 156 | self.Y_G = self.response_vectors[2 * self.num_q + self.num_G :] 157 | self.Z_q_normed = np.zeros_like(self.Z_q) 158 | self.Z_G_normed = np.zeros_like(self.Z_G) 159 | self.Y_q_normed = np.zeros_like(self.Y_q) 160 | self.Y_G_normed = np.zeros_like(self.Y_G) 161 | norms = self.get_excited_state_norm() 162 | for state_number, norm in enumerate(norms): 163 | if norm < 10**-10: 164 | print(f"WARNING: State number {state_number} could not be normalized. Norm of {norm}.") 165 | continue 166 | self.Z_q_normed[:, state_number] = self.Z_q[:, state_number] * (1 / norm) ** 0.5 167 | self.Z_G_normed[:, state_number] = self.Z_G[:, state_number] * (1 / norm) ** 0.5 168 | self.Y_q_normed[:, state_number] = self.Y_q[:, state_number] * (1 / norm) ** 0.5 169 | self.Y_G_normed[:, state_number] = self.Y_G[:, state_number] * (1 / norm) ** 0.5 170 | self.normed_response_vectors[:, state_number] = ( 171 | self.response_vectors[:, state_number] * (1 / norm) ** 0.5 172 | ) 173 | 174 | def get_excited_state_norm(self) -> np.ndarray: 175 | """Calculate the norm of excited states. 176 | 177 | Returns: 178 | Norm of excited states. 179 | """ 180 | norms = np.zeros(len(self.response_vectors[0])) 181 | for state_number in range(len(self.response_vectors[0])): 182 | # Get Z_q Z_G Y_q and Y_G matrices 183 | ZZq = np.outer(self.Z_q[:, state_number], self.Z_q[:, state_number].transpose()) 184 | YYq = np.outer(self.Y_q[:, state_number], self.Y_q[:, state_number].transpose()) 185 | ZZG = np.outer(self.Z_G[:, state_number], self.Z_G[:, state_number].transpose()) 186 | YYG = np.outer(self.Y_G[:, state_number], self.Y_G[:, state_number].transpose()) 187 | 188 | norms[state_number] = np.sum(self.metric[: self.num_q, : self.num_q] * (ZZq - YYq)) + np.sum( 189 | self.metric[self.num_q : self.num_q + self.num_G, self.num_q : self.num_q + self.num_G] 190 | * (ZZG - YYG) 191 | ) 192 | 193 | return norms 194 | 195 | def get_transition_dipole(self, dipole_integrals: Sequence[np.ndarray]) -> np.ndarray: 196 | """Calculate transition dipole moment. 197 | 198 | Args: 199 | dipole_integrals: Dipole integrals (x,y,z) in AO basis. 200 | 201 | Returns: 202 | Transition dipole moment. 203 | """ 204 | raise NotImplementedError 205 | 206 | def get_oscillator_strength(self, dipole_integrals: Sequence[np.ndarray]) -> np.ndarray: 207 | r"""Calculate oscillator strength. 208 | 209 | .. math:: 210 | f_n = \frac{2}{3}e_n\left|\left<0\left|\hat{\mu}\right|n\right>\right|^2 211 | 212 | Args: 213 | dipole_integrals: Dipole integrals (x,y,z) in AO basis. 214 | 215 | Returns: 216 | Oscillator Strength. 217 | """ 218 | transition_dipoles = self.get_transition_dipole(dipole_integrals) 219 | osc_strs = np.zeros(len(transition_dipoles)) 220 | for idx, (excitation_energy, transition_dipole) in enumerate( 221 | zip(self.excitation_energies, transition_dipoles) 222 | ): 223 | osc_strs[idx] = ( 224 | 2 225 | / 3 226 | * excitation_energy 227 | * (transition_dipole[0] ** 2 + transition_dipole[1] ** 2 + transition_dipole[2] ** 2) 228 | ) 229 | self.oscillator_strengths = osc_strs 230 | return osc_strs 231 | 232 | def get_formatted_oscillator_strength(self) -> str: 233 | """Create table of excitation energies and oscillator strengths. 234 | 235 | Args: 236 | dipole_integrals: Dipole integrals (x,y,z) in AO basis. 237 | 238 | Returns: 239 | Nicely formatted table. 240 | """ 241 | if not hasattr(self, "oscillator_strengths"): 242 | raise ValueError( 243 | "Oscillator strengths have not been calculated. Run get_oscillator_strength() first." 244 | ) 245 | 246 | output = ( 247 | "Excitation # | Excitation energy [Hartree] | Excitation energy [eV] | Oscillator strengths\n" 248 | ) 249 | 250 | for i, (exc_energy, osc_strength) in enumerate( 251 | zip(self.excitation_energies, self.oscillator_strengths) 252 | ): 253 | exc_str = f"{exc_energy:2.6f}" 254 | exc_str_ev = f"{exc_energy * 27.2114079527:3.6f}" 255 | osc_str = f"{osc_strength:1.6f}" 256 | output += f"{str(i + 1).center(12)} | {exc_str.center(27)} | {exc_str_ev.center(22)} | {osc_str.center(20)}\n" 257 | return output 258 | -------------------------------------------------------------------------------- /slowquant/unitary_coupled_cluster/linear_response/projected_statetransfer.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | 3 | import numpy as np 4 | 5 | from slowquant.molecularintegrals.integralfunctions import ( 6 | one_electron_integral_transform, 7 | ) 8 | from slowquant.unitary_coupled_cluster.density_matrix import ( 9 | ReducedDenstiyMatrix, 10 | get_orbital_gradient_response, 11 | get_orbital_response_property_gradient, 12 | ) 13 | from slowquant.unitary_coupled_cluster.linear_response.lr_baseclass import ( 14 | LinearResponseBaseClass, 15 | ) 16 | from slowquant.unitary_coupled_cluster.operator_state_algebra import ( 17 | expectation_value, 18 | propagate_state, 19 | ) 20 | from slowquant.unitary_coupled_cluster.operators import ( 21 | hamiltonian_2i_2a, 22 | one_elec_op_0i_0a, 23 | ) 24 | from slowquant.unitary_coupled_cluster.ucc_wavefunction import WaveFunctionUCC 25 | from slowquant.unitary_coupled_cluster.ups_wavefunction import WaveFunctionUPS 26 | 27 | 28 | class LinearResponseUCC(LinearResponseBaseClass): 29 | def __init__( 30 | self, 31 | wave_function: WaveFunctionUCC | WaveFunctionUPS, 32 | excitations: str, 33 | ) -> None: 34 | """Initialize linear response by calculating the needed matrices. 35 | 36 | Args: 37 | wave_function: Wave function object. 38 | excitations: Which excitation orders to include in response. 39 | """ 40 | super().__init__(wave_function, excitations) 41 | 42 | H_2i_2a = hamiltonian_2i_2a( 43 | self.wf.h_mo, 44 | self.wf.g_mo, 45 | self.wf.num_inactive_orbs, 46 | self.wf.num_active_orbs, 47 | self.wf.num_virtual_orbs, 48 | ) 49 | 50 | rdms = ReducedDenstiyMatrix( 51 | self.wf.num_inactive_orbs, 52 | self.wf.num_active_orbs, 53 | self.wf.num_virtual_orbs, 54 | self.wf.rdm1, 55 | rdm2=self.wf.rdm2, 56 | ) 57 | idx_shift = len(self.q_ops) 58 | print("Gs", len(self.G_ops)) 59 | print("qs", len(self.q_ops)) 60 | grad = get_orbital_gradient_response( # proj-q and naive-q lead to same working equations 61 | rdms, 62 | self.wf.h_mo, 63 | self.wf.g_mo, 64 | self.wf.kappa_no_activeactive_idx, 65 | self.wf.num_inactive_orbs, 66 | self.wf.num_active_orbs, 67 | ) 68 | if len(grad) != 0: 69 | print("idx, max(abs(grad orb)):", np.argmax(np.abs(grad)), np.max(np.abs(grad))) 70 | if np.max(np.abs(grad)) > 10**-3: 71 | raise ValueError("Large Gradient detected in q of ", np.max(np.abs(grad))) 72 | grad = np.zeros(2 * len(self.G_ops)) 73 | UdH00_ket = propagate_state(["Ud", self.H_0i_0a], self.wf.ci_coeffs, *self.index_info) 74 | for i, op in enumerate(self.G_ops): 75 | G_ket = propagate_state( 76 | [op], 77 | self.wf.csf_coeffs, 78 | *self.index_info, 79 | ) 80 | # <0| H U G |CSF> 81 | grad[i] = -expectation_value( 82 | UdH00_ket, 83 | [], 84 | G_ket, 85 | *self.index_info, 86 | ) 87 | # 88 | grad[i + len(self.G_ops)] = expectation_value( 89 | G_ket, 90 | [], 91 | UdH00_ket, 92 | *self.index_info, 93 | ) 94 | if len(grad) != 0: 95 | print("idx, max(abs(grad active)):", np.argmax(np.abs(grad)), np.max(np.abs(grad))) 96 | if np.max(np.abs(grad)) > 10**-3: 97 | raise ValueError("Large Gradient detected in G of ", np.max(np.abs(grad))) 98 | for j, qJ in enumerate(self.q_ops): 99 | for i, qI in enumerate(self.q_ops[j:], j): 100 | # Make A 101 | val = expectation_value( 102 | self.wf.ci_coeffs, 103 | [qI.dagger * H_2i_2a * qJ], 104 | self.wf.ci_coeffs, 105 | *self.index_info, 106 | ) 107 | val -= ( 108 | expectation_value( 109 | self.wf.ci_coeffs, [qI.dagger * qJ], self.wf.ci_coeffs, *self.index_info 110 | ) 111 | * self.wf.energy_elec 112 | ) 113 | self.A[i, j] = self.A[j, i] = val 114 | # Make Sigma 115 | self.Sigma[i, j] = self.Sigma[j, i] = expectation_value( 116 | self.wf.ci_coeffs, 117 | [qI.dagger * qJ], 118 | self.wf.ci_coeffs, 119 | *self.index_info, 120 | ) 121 | for j, qJ in enumerate(self.q_ops): 122 | UdHq_ket = propagate_state(["Ud", self.H_1i_1a * qJ], self.wf.ci_coeffs, *self.index_info) 123 | for i, GI in enumerate(self.G_ops): 124 | G_ket = propagate_state([GI], self.wf.csf_coeffs, *self.index_info) 125 | # Make A 126 | # 127 | val = expectation_value( 128 | G_ket, 129 | [], 130 | UdHq_ket, 131 | *self.index_info, 132 | ) 133 | self.A[j, i + idx_shift] = self.A[i + idx_shift, j] = val 134 | for j, GJ in enumerate(self.G_ops): 135 | UdHUGJ_ket = propagate_state( 136 | ["Ud", self.H_0i_0a, "U", GJ], 137 | self.wf.csf_coeffs, 138 | *self.index_info, 139 | ) 140 | for i, GI in enumerate(self.G_ops[j:], j): 141 | # Make A 142 | # 143 | val = expectation_value( 144 | self.wf.csf_coeffs, 145 | [GI.dagger], 146 | UdHUGJ_ket, 147 | *self.index_info, 148 | ) 149 | if i == j: 150 | val -= self.wf.energy_elec 151 | self.A[i + idx_shift, j + idx_shift] = self.A[j + idx_shift, i + idx_shift] = val 152 | # Make Sigma 153 | if i == j: 154 | self.Sigma[i + idx_shift, j + idx_shift] = 1 155 | 156 | def get_transition_dipole(self, dipole_integrals: Sequence[np.ndarray]) -> np.ndarray: 157 | """Calculate transition dipole moment. 158 | 159 | Args: 160 | dipole_integrals: Dipole integrals ordered as (x,y,z). 161 | 162 | Returns: 163 | Transition dipole moment. 164 | """ 165 | if len(dipole_integrals) != 3: 166 | raise ValueError(f"Expected 3 dipole integrals got {len(dipole_integrals)}") 167 | number_excitations = len(self.excitation_energies) 168 | rdms = ReducedDenstiyMatrix( 169 | self.wf.num_inactive_orbs, 170 | self.wf.num_active_orbs, 171 | self.wf.num_virtual_orbs, 172 | self.wf.rdm1, 173 | rdm2=self.wf.rdm2, 174 | ) 175 | mux = one_electron_integral_transform(self.wf.c_mo, dipole_integrals[0]) 176 | muy = one_electron_integral_transform(self.wf.c_mo, dipole_integrals[1]) 177 | muz = one_electron_integral_transform(self.wf.c_mo, dipole_integrals[2]) 178 | mux_op = one_elec_op_0i_0a( 179 | mux, 180 | self.wf.num_inactive_orbs, 181 | self.wf.num_active_orbs, 182 | ) 183 | muy_op = one_elec_op_0i_0a( 184 | muy, 185 | self.wf.num_inactive_orbs, 186 | self.wf.num_active_orbs, 187 | ) 188 | muz_op = one_elec_op_0i_0a( 189 | muz, 190 | self.wf.num_inactive_orbs, 191 | self.wf.num_active_orbs, 192 | ) 193 | Udmuxd_ket = propagate_state(["Ud", mux_op.dagger], self.wf.ci_coeffs, *self.index_info) 194 | Udmuyd_ket = propagate_state(["Ud", muy_op.dagger], self.wf.ci_coeffs, *self.index_info) 195 | Udmuzd_ket = propagate_state(["Ud", muz_op.dagger], self.wf.ci_coeffs, *self.index_info) 196 | Udmux_ket = propagate_state(["Ud", mux_op], self.wf.ci_coeffs, *self.index_info) 197 | Udmuy_ket = propagate_state(["Ud", muy_op], self.wf.ci_coeffs, *self.index_info) 198 | Udmuz_ket = propagate_state(["Ud", muz_op], self.wf.ci_coeffs, *self.index_info) 199 | transition_dipoles = np.zeros((len(self.normed_response_vectors[0]), 3)) 200 | for state_number in range(len(self.normed_response_vectors[0])): 201 | q_part_x = get_orbital_response_property_gradient( 202 | rdms, 203 | mux, 204 | self.wf.kappa_no_activeactive_idx, 205 | self.wf.num_inactive_orbs, 206 | self.wf.num_active_orbs, 207 | self.normed_response_vectors, 208 | state_number, 209 | number_excitations, 210 | ) 211 | q_part_y = get_orbital_response_property_gradient( 212 | rdms, 213 | muy, 214 | self.wf.kappa_no_activeactive_idx, 215 | self.wf.num_inactive_orbs, 216 | self.wf.num_active_orbs, 217 | self.normed_response_vectors, 218 | state_number, 219 | number_excitations, 220 | ) 221 | q_part_z = get_orbital_response_property_gradient( 222 | rdms, 223 | muz, 224 | self.wf.kappa_no_activeactive_idx, 225 | self.wf.num_inactive_orbs, 226 | self.wf.num_active_orbs, 227 | self.normed_response_vectors, 228 | state_number, 229 | number_excitations, 230 | ) 231 | g_part_x = 0.0 232 | g_part_y = 0.0 233 | g_part_z = 0.0 234 | for i, G in enumerate(self.G_ops): 235 | G_ket = propagate_state( 236 | [G], 237 | self.wf.csf_coeffs, 238 | *self.index_info, 239 | ) 240 | # -Z * <0| mux U G | CSF> 241 | g_part_x -= self.Z_G_normed[i, state_number] * expectation_value( 242 | Udmuxd_ket, 243 | [], 244 | G_ket, 245 | *self.index_info, 246 | ) 247 | # Y * <0| Gd Ud mux | CSF> 248 | g_part_x += self.Y_G_normed[i, state_number] * expectation_value( 249 | G_ket, 250 | [], 251 | Udmux_ket, 252 | *self.index_info, 253 | ) 254 | # -Z * <0| muy U G | CSF> 255 | g_part_y -= self.Z_G_normed[i, state_number] * expectation_value( 256 | Udmuyd_ket, 257 | [], 258 | G_ket, 259 | *self.index_info, 260 | ) 261 | # Y * <0| Gd Ud muy | CSF> 262 | g_part_y += self.Y_G_normed[i, state_number] * expectation_value( 263 | G_ket, 264 | [], 265 | Udmuy_ket, 266 | *self.index_info, 267 | ) 268 | # -Z * <0| muz U G | CSF> 269 | g_part_z -= self.Z_G_normed[i, state_number] * expectation_value( 270 | Udmuzd_ket, 271 | [], 272 | G_ket, 273 | *self.index_info, 274 | ) 275 | # Y * <0| Gd Ud muz | CSF> 276 | g_part_z += self.Y_G_normed[i, state_number] * expectation_value( 277 | G_ket, 278 | [], 279 | Udmuz_ket, 280 | *self.index_info, 281 | ) 282 | transition_dipoles[state_number, 0] = q_part_x + g_part_x 283 | transition_dipoles[state_number, 1] = q_part_y + g_part_y 284 | transition_dipoles[state_number, 2] = q_part_z + g_part_z 285 | return transition_dipoles 286 | -------------------------------------------------------------------------------- /slowquant/unitary_coupled_cluster/linear_response/statetransfer.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Sequence 2 | 3 | import numpy as np 4 | 5 | from slowquant.molecularintegrals.integralfunctions import ( 6 | one_electron_integral_transform, 7 | ) 8 | from slowquant.unitary_coupled_cluster.density_matrix import ( 9 | ReducedDenstiyMatrix, 10 | get_orbital_gradient_response, 11 | get_orbital_response_hessian_block, 12 | get_orbital_response_metric_sigma, 13 | get_orbital_response_property_gradient, 14 | ) 15 | from slowquant.unitary_coupled_cluster.linear_response.lr_baseclass import ( 16 | LinearResponseBaseClass, 17 | ) 18 | from slowquant.unitary_coupled_cluster.operator_state_algebra import ( 19 | expectation_value, 20 | propagate_state, 21 | ) 22 | from slowquant.unitary_coupled_cluster.operators import one_elec_op_0i_0a 23 | from slowquant.unitary_coupled_cluster.ucc_wavefunction import WaveFunctionUCC 24 | from slowquant.unitary_coupled_cluster.ups_wavefunction import WaveFunctionUPS 25 | 26 | 27 | class LinearResponseUCC(LinearResponseBaseClass): 28 | def __init__( 29 | self, 30 | wave_function: WaveFunctionUCC | WaveFunctionUPS, 31 | excitations: str, 32 | ) -> None: 33 | """Initialize linear response by calculating the needed matrices. 34 | 35 | Args: 36 | wave_function: Wave function object. 37 | excitations: Which excitation orders to include in response. 38 | """ 39 | super().__init__(wave_function, excitations) 40 | 41 | rdms = ReducedDenstiyMatrix( 42 | self.wf.num_inactive_orbs, 43 | self.wf.num_active_orbs, 44 | self.wf.num_virtual_orbs, 45 | self.wf.rdm1, 46 | rdm2=self.wf.rdm2, 47 | ) 48 | idx_shift = len(self.q_ops) 49 | print("Gs", len(self.G_ops)) 50 | print("qs", len(self.q_ops)) 51 | grad = get_orbital_gradient_response( 52 | rdms, 53 | self.wf.h_mo, 54 | self.wf.g_mo, 55 | self.wf.kappa_no_activeactive_idx, 56 | self.wf.num_inactive_orbs, 57 | self.wf.num_active_orbs, 58 | ) 59 | if len(grad) != 0: 60 | print("idx, max(abs(grad orb)):", np.argmax(np.abs(grad)), np.max(np.abs(grad))) 61 | if np.max(np.abs(grad)) > 10**-3: 62 | raise ValueError("Large Gradient detected in q of ", np.max(np.abs(grad))) 63 | grad = np.zeros(2 * len(self.G_ops)) 64 | UdH00_ket = propagate_state(["Ud", self.H_0i_0a], self.wf.ci_coeffs, *self.index_info) 65 | for i, op in enumerate(self.G_ops): 66 | G_ket = propagate_state( 67 | [op], 68 | self.wf.csf_coeffs, 69 | *self.index_info, 70 | ) 71 | # - <0| H U G |CSF> 72 | grad[i] = -expectation_value( 73 | UdH00_ket, 74 | [], 75 | G_ket, 76 | *self.index_info, 77 | ) 78 | # <0| Gd Ud H |0> 79 | grad[i + len(self.G_ops)] = expectation_value( 80 | G_ket, 81 | [], 82 | UdH00_ket, 83 | *self.index_info, 84 | ) 85 | if len(grad) != 0: 86 | print("idx, max(abs(grad active)):", np.argmax(np.abs(grad)), np.max(np.abs(grad))) 87 | if np.max(np.abs(grad)) > 10**-3: 88 | raise ValueError("Large Gradient detected in G of ", np.max(np.abs(grad))) 89 | # Do orbital-orbital blocks 90 | self.A[: len(self.q_ops), : len(self.q_ops)] = get_orbital_response_hessian_block( 91 | rdms, 92 | self.wf.h_mo, 93 | self.wf.g_mo, 94 | self.wf.kappa_no_activeactive_idx_dagger, 95 | self.wf.kappa_no_activeactive_idx, 96 | self.wf.num_inactive_orbs, 97 | self.wf.num_active_orbs, 98 | ) 99 | self.B[: len(self.q_ops), : len(self.q_ops)] = get_orbital_response_hessian_block( 100 | rdms, 101 | self.wf.h_mo, 102 | self.wf.g_mo, 103 | self.wf.kappa_no_activeactive_idx_dagger, 104 | self.wf.kappa_no_activeactive_idx_dagger, 105 | self.wf.num_inactive_orbs, 106 | self.wf.num_active_orbs, 107 | ) 108 | self.Sigma[: len(self.q_ops), : len(self.q_ops)] = get_orbital_response_metric_sigma( 109 | rdms, self.wf.kappa_no_activeactive_idx 110 | ) 111 | for j, qJ in enumerate(self.q_ops): 112 | UdHq_ket = propagate_state(["Ud", self.H_1i_1a * qJ], self.wf.ci_coeffs, *self.index_info) 113 | UdqdH_ket = propagate_state(["Ud", qJ.dagger * self.H_1i_1a], self.wf.ci_coeffs, *self.index_info) 114 | for i, GI in enumerate(self.G_ops): 115 | G_ket = propagate_state([GI], self.wf.csf_coeffs, *self.index_info) 116 | # Make A 117 | # 118 | val = expectation_value( 119 | G_ket, 120 | [], 121 | UdHq_ket, 122 | *self.index_info, 123 | ) 124 | self.A[j, i + idx_shift] = self.A[i + idx_shift, j] = val 125 | # Make B 126 | # - 1/2 127 | val = ( 128 | -1 129 | / 2 130 | * expectation_value( 131 | G_ket, 132 | [], 133 | UdqdH_ket, 134 | *self.index_info, 135 | ) 136 | ) 137 | self.B[j, i + idx_shift] = self.B[i + idx_shift, j] = val 138 | for j, GJ in enumerate(self.G_ops): 139 | UdHUGJ = propagate_state( 140 | ["Ud", self.H_0i_0a, "U", GJ], 141 | self.wf.csf_coeffs, 142 | *self.index_info, 143 | ) 144 | for i, GI in enumerate(self.G_ops[j:], j): 145 | # Make A 146 | # 147 | val = expectation_value( 148 | self.wf.csf_coeffs, 149 | [GI.dagger], 150 | UdHUGJ, 151 | *self.index_info, 152 | ) 153 | if i == j: 154 | val -= self.wf.energy_elec 155 | self.A[i + idx_shift, j + idx_shift] = self.A[j + idx_shift, i + idx_shift] = val 156 | # Make Sigma 157 | if i == j: 158 | self.Sigma[i + idx_shift, j + idx_shift] = 1 159 | 160 | def get_transition_dipole(self, dipole_integrals: Sequence[np.ndarray]) -> np.ndarray: 161 | """Calculate transition dipole moment. 162 | 163 | Args: 164 | dipole_integrals: Dipole integrals ordered as (x,y,z). 165 | 166 | Returns: 167 | Transition dipole moment. 168 | """ 169 | if len(dipole_integrals) != 3: 170 | raise ValueError(f"Expected 3 dipole integrals got {len(dipole_integrals)}") 171 | number_excitations = len(self.excitation_energies) 172 | rdms = ReducedDenstiyMatrix( 173 | self.wf.num_inactive_orbs, 174 | self.wf.num_active_orbs, 175 | self.wf.num_virtual_orbs, 176 | self.wf.rdm1, 177 | rdm2=self.wf.rdm2, 178 | ) 179 | mux = one_electron_integral_transform(self.wf.c_mo, dipole_integrals[0]) 180 | muy = one_electron_integral_transform(self.wf.c_mo, dipole_integrals[1]) 181 | muz = one_electron_integral_transform(self.wf.c_mo, dipole_integrals[2]) 182 | mux_op = one_elec_op_0i_0a( 183 | mux, 184 | self.wf.num_inactive_orbs, 185 | self.wf.num_active_orbs, 186 | ) 187 | muy_op = one_elec_op_0i_0a( 188 | muy, 189 | self.wf.num_inactive_orbs, 190 | self.wf.num_active_orbs, 191 | ) 192 | muz_op = one_elec_op_0i_0a( 193 | muz, 194 | self.wf.num_inactive_orbs, 195 | self.wf.num_active_orbs, 196 | ) 197 | Udmuxd_ket = propagate_state(["Ud", mux_op.dagger], self.wf.ci_coeffs, *self.index_info) 198 | Udmuyd_ket = propagate_state(["Ud", muy_op.dagger], self.wf.ci_coeffs, *self.index_info) 199 | Udmuzd_ket = propagate_state(["Ud", muz_op.dagger], self.wf.ci_coeffs, *self.index_info) 200 | Udmux_ket = propagate_state(["Ud", mux_op], self.wf.ci_coeffs, *self.index_info) 201 | Udmuy_ket = propagate_state(["Ud", muy_op], self.wf.ci_coeffs, *self.index_info) 202 | Udmuz_ket = propagate_state(["Ud", muz_op], self.wf.ci_coeffs, *self.index_info) 203 | transition_dipoles = np.zeros((len(self.normed_response_vectors[0]), 3)) 204 | for state_number in range(len(self.normed_response_vectors[0])): 205 | q_part_x = get_orbital_response_property_gradient( 206 | rdms, 207 | mux, 208 | self.wf.kappa_no_activeactive_idx, 209 | self.wf.num_inactive_orbs, 210 | self.wf.num_active_orbs, 211 | self.normed_response_vectors, 212 | state_number, 213 | number_excitations, 214 | ) 215 | q_part_y = get_orbital_response_property_gradient( 216 | rdms, 217 | muy, 218 | self.wf.kappa_no_activeactive_idx, 219 | self.wf.num_inactive_orbs, 220 | self.wf.num_active_orbs, 221 | self.normed_response_vectors, 222 | state_number, 223 | number_excitations, 224 | ) 225 | q_part_z = get_orbital_response_property_gradient( 226 | rdms, 227 | muz, 228 | self.wf.kappa_no_activeactive_idx, 229 | self.wf.num_inactive_orbs, 230 | self.wf.num_active_orbs, 231 | self.normed_response_vectors, 232 | state_number, 233 | number_excitations, 234 | ) 235 | g_part_x = 0.0 236 | g_part_y = 0.0 237 | g_part_z = 0.0 238 | for i, G in enumerate(self.G_ops): 239 | G_ket = propagate_state( 240 | [G], 241 | self.wf.csf_coeffs, 242 | *self.index_info, 243 | ) 244 | # -Z * <0| mux U G | CSF> 245 | g_part_x -= self.Z_G_normed[i, state_number] * expectation_value( 246 | Udmuxd_ket, 247 | [], 248 | G_ket, 249 | *self.index_info, 250 | ) 251 | # Y * <0| Gd Ud mux | CSF> 252 | g_part_x += self.Y_G_normed[i, state_number] * expectation_value( 253 | G_ket, 254 | [], 255 | Udmux_ket, 256 | *self.index_info, 257 | ) 258 | # -Z * <0| muy U G | CSF> 259 | g_part_y -= self.Z_G_normed[i, state_number] * expectation_value( 260 | Udmuyd_ket, 261 | [], 262 | G_ket, 263 | *self.index_info, 264 | ) 265 | # Y * <0| Gd Ud muy | CSF> 266 | g_part_y += self.Y_G_normed[i, state_number] * expectation_value( 267 | G_ket, 268 | [], 269 | Udmuy_ket, 270 | *self.index_info, 271 | ) 272 | # -Z * <0| muz U G | CSF> 273 | g_part_z -= self.Z_G_normed[i, state_number] * expectation_value( 274 | Udmuzd_ket, 275 | [], 276 | G_ket, 277 | *self.index_info, 278 | ) 279 | # Y * <0| Gd Ud muz | CSF> 280 | g_part_z += self.Y_G_normed[i, state_number] * expectation_value( 281 | G_ket, 282 | [], 283 | Udmuz_ket, 284 | *self.index_info, 285 | ) 286 | transition_dipoles[state_number, 0] = q_part_x + g_part_x 287 | transition_dipoles[state_number, 1] = q_part_y + g_part_y 288 | transition_dipoles[state_number, 2] = q_part_z + g_part_z 289 | return transition_dipoles 290 | -------------------------------------------------------------------------------- /slowquant/unitary_coupled_cluster/optimizers.py: -------------------------------------------------------------------------------- 1 | import time 2 | from collections.abc import Callable, Sequence 3 | from functools import partial 4 | from typing import Any 5 | 6 | import numba as nb 7 | import numpy as np 8 | import scipy 9 | 10 | 11 | class Result: 12 | """Result class for optimizers.""" 13 | 14 | def __init__(self) -> None: 15 | """Initialize result class.""" 16 | self.x: np.ndarray 17 | self.fun: float 18 | 19 | 20 | class Optimizers: 21 | """Optimizers class.""" 22 | 23 | _start: float 24 | _iteration: int 25 | 26 | def __init__( 27 | self, 28 | fun: Callable[[list[float]], float | np.ndarray], 29 | method: str, 30 | grad: Callable[[list[float]], np.ndarray] | None = None, 31 | maxiter: int = 1000, 32 | tol: float = 10e-8, 33 | is_silent: bool = False, 34 | ) -> None: 35 | """Initialize optimizer class. 36 | 37 | Args: 38 | fun: Function to minimize. 39 | method: Optimization method. 40 | grad: Gradient of function. 41 | maxiter: Maximum iterations. 42 | tol: Convergence tolerance. 43 | is_silent: Suppress progress output. 44 | """ 45 | self.fun = fun 46 | self.grad = grad 47 | self.method = method.lower() 48 | self.maxiter = maxiter 49 | self.tol = tol 50 | self.is_silent = is_silent 51 | 52 | def _print_progress( 53 | self, x: Sequence[float], fun: Callable[[list[float]], float | np.ndarray], silent: bool = False 54 | ) -> None: 55 | """Print progress during optimization. 56 | 57 | Args: 58 | x: Parameters. 59 | fun: Function. 60 | silent: Silence progress print. 61 | """ 62 | if not silent: 63 | e = fun(list(x)) 64 | if isinstance(e, np.ndarray): 65 | e_str = f"{np.mean(e):3.16f}" 66 | else: 67 | e_str = f"{e:3.16f}" 68 | time_str = f"{time.time() - self._start:7.2f}" 69 | print( 70 | f"--------{str(self._iteration + 1).center(11)} | {time_str.center(18)} | {e_str.center(27)}" 71 | ) 72 | self._iteration += 1 73 | self._start = time.time() 74 | 75 | def minimize(self, x0: Sequence[float], extra_options: dict[str, Any] | None = None) -> Result: 76 | """Minimize function. 77 | 78 | extra_options: 79 | * R dict[str, int]: Order parameter needed for Rotosolve. 80 | * param_names Sequence[str]: Names of parameters needed for Rotosolve. 81 | 82 | Args: 83 | x0: Starting value of changeable parameters. 84 | extra_options: Extra options for optimizers. 85 | """ 86 | self._start = time.time() 87 | self._iteration = 0 88 | print_progress = partial(self._print_progress, fun=self.fun, silent=self.is_silent) 89 | if self.method in ("bfgs", "l-bfgs-b", "slsqp"): 90 | if self.grad is not None: 91 | res = scipy.optimize.minimize( 92 | self.fun, 93 | x0, 94 | jac=self.grad, 95 | method=self.method, 96 | tol=self.tol, 97 | callback=print_progress, 98 | options={"maxiter": self.maxiter}, 99 | ) 100 | else: 101 | res = scipy.optimize.minimize( 102 | self.fun, 103 | x0, 104 | method=self.method, 105 | tol=self.tol, 106 | callback=print_progress, 107 | options={"maxiter": self.maxiter}, 108 | ) 109 | elif self.method in ("cobyla", "cobyqa"): 110 | res = scipy.optimize.minimize( 111 | self.fun, 112 | x0, 113 | method=self.method, 114 | tol=self.tol, 115 | callback=print_progress, 116 | options={"maxiter": self.maxiter}, 117 | ) 118 | elif self.method in ("rotosolve",): 119 | if not isinstance(extra_options, dict): 120 | raise TypeError("extra_options is not set, but is required for RotoSolve") 121 | if "R" not in extra_options: 122 | raise ValueError(f"Expected option 'R' in extra_options, got {extra_options.keys()}") 123 | if "param_names" not in extra_options: 124 | raise ValueError( 125 | f"Expected option 'param_names' in extra_options, got {extra_options.keys()}" 126 | ) 127 | optimizer = RotoSolve( 128 | extra_options["R"], 129 | extra_options["param_names"], 130 | maxiter=self.maxiter, 131 | tol=self.tol, 132 | callback=print_progress, 133 | ) 134 | res = optimizer.minimize(self.fun, x0) 135 | 136 | else: 137 | raise ValueError(f"Got an unkonwn optimizer {self.method}") 138 | result = Result() 139 | result.x = res.x 140 | result.fun = res.fun 141 | return result 142 | 143 | 144 | class RotoSolve: 145 | r"""Rotosolve optimizer. 146 | 147 | Implementation of Rotosolver assuming three eigenvalues for generators. 148 | This works for fermionic generators of the type: 149 | 150 | .. math:: 151 | \hat{G}_{pq} = \hat{a}^\dagger_p \hat{a}_q - \hat{a}_q^\dagger \hat{a}_p 152 | 153 | and, 154 | 155 | .. math:: 156 | \hat{G}_{pqrs} = \hat{a}^\dagger_p \hat{a}^\dagger_q \hat{a}_r \hat{a}_s - \hat{a}^\dagger_s \hat{a}^\dagger_r \hat{a}_p \hat{a}_q 157 | 158 | Rotosolve works by exactly reconstructing the energy function in a single parameter: 159 | 160 | .. math:: 161 | E(x) = \frac{\sin\left(\frac{2R+1}{2}x\right)}{2R+1}\sum_{\mu=-R}^{R}E(x_\mu)\frac{(-1)^\mu}{\sin\left(\frac{x - x_\mu}{2}\right)} 162 | 163 | With :math:`R` being the number of different positive differences between eigenvalues, and :math:`x_\mu=\frac{2\mu}{2R+1}\pi`. 164 | 165 | After the function :math:`E(x)` have been reconstructed the global minima of the function can be found classically. 166 | 167 | #. 10.22331/q-2021-01-28-391, Algorithm 1 168 | #. 10.22331/q-2022-03-30-677, Eq. (57) 169 | """ 170 | 171 | def __init__( 172 | self, 173 | R: dict[str, int], 174 | param_names: Sequence[str], 175 | maxiter: int = 30, 176 | tol: float = 1e-6, 177 | callback: Callable[[list[float]], None] | None = None, 178 | ) -> None: 179 | """Initialize Rotosolver. 180 | 181 | Args: 182 | R: R parameter used for the function reconstruction. 183 | param_names: Names of parameters, used to index R. 184 | maxiter: Maximum number of iterations (sweeps). 185 | tol: Convergence tolerance. 186 | callback: Callback function, takes only x (parameters) as an argument. 187 | """ 188 | self._callback = callback 189 | self.max_iterations = maxiter 190 | self.threshold = tol 191 | self.max_fail = 3 192 | self._R = R 193 | self._param_names = param_names 194 | 195 | def minimize(self, f: Callable[[list[float]], float | np.ndarray], x0: Sequence[float]) -> Result: 196 | """Run minimization. 197 | 198 | Args: 199 | f: Function to be minimized, can only take one argument. 200 | x0: Initial guess of changeable parameters of f. 201 | 202 | Returns: 203 | Minimization results. 204 | """ 205 | f_best = float(10**20) 206 | x = list(x0).copy() 207 | x_best = x.copy() 208 | fails = 0 209 | res = Result() 210 | for _ in range(self.max_iterations): 211 | for i, par_name in enumerate(self._param_names): 212 | # Get the energy for specific values of theta_i, defined by the _R parameter. 213 | e_vals = get_energy_evals(f, x, i, self._R[par_name]) 214 | # Do an analytic construction of the energy as a function of theta_i. 215 | f_reconstructed = partial(reconstructed_f, energy_vals=e_vals, R=self._R[par_name]) 216 | # Evaluate the energy in many points. 217 | values = f_reconstructed(np.linspace(-np.pi, np.pi, int(1e4))) 218 | # Find the theta_i that gives the lowest energy. 219 | theta = np.linspace(-np.pi, np.pi, int(1e4))[np.argmin(values)] 220 | # Run an optimization on the theta_i that gives to the lowest energy in the previous step. 221 | # This is to get more digits precision in value of theta_i. 222 | res = scipy.optimize.minimize(f_reconstructed, x0=[theta], method="BFGS", tol=1e-12) 223 | x[i] = res.x[0] 224 | while x[i] < np.pi: 225 | x[i] += 2 * np.pi 226 | while x[i] > np.pi: 227 | x[i] -= 2 * np.pi 228 | f_tmp = f(x) 229 | if isinstance(f_tmp, np.ndarray): 230 | # State-averaged case 231 | f_new = float(np.mean(f_tmp)) 232 | else: 233 | # Single state case 234 | f_new = f_tmp 235 | if abs(f_best - f_new) < self.threshold: 236 | f_best = f_new 237 | x_best = x.copy() 238 | break 239 | if (f_new - f_best) > 0.0: 240 | fails += 1 241 | else: 242 | f_best = f_new 243 | x_best = x.copy() 244 | if fails == self.max_fail: 245 | break 246 | if self._callback is not None: 247 | self._callback(x) 248 | res.x = np.array(x_best) 249 | res.fun = f_best 250 | return res 251 | 252 | 253 | def get_energy_evals( 254 | f: Callable[[list[float]], float | np.ndarray], x: list[float], idx: int, R: int 255 | ) -> list[float] | list[np.ndarray]: 256 | """Evaluate the function in all points needed for the reconstruction in Rotosolve. 257 | 258 | Args: 259 | f: Function to evaluate. 260 | x: Parameters of f. 261 | idx: Index of parameter to be changed. 262 | R: Parameter to control how many points are needed. 263 | 264 | Returns: 265 | All needed function evaluations. 266 | """ 267 | e_vals = [] 268 | x = x.copy() 269 | for mu in range(-R, R + 1): 270 | x_mu = 2 * mu / (2 * R + 1) * np.pi 271 | x[idx] = x_mu 272 | e_vals.append(f(x)) 273 | return e_vals # type: ignore 274 | 275 | 276 | @nb.jit(nopython=True) 277 | def reconstructed_f(x_vals: np.ndarray, energy_vals: list[float] | list[np.ndarray], R: int) -> np.ndarray: 278 | r"""Reconstructed the function in terms of sin-functions. 279 | 280 | .. math:: 281 | E(x) = \frac{\sin\left(\frac{2R+1}{2}x\right)}{2R+1}\sum_{\mu=-R}^{R}E(x_\mu)\frac{(-1)^\mu}{\sin\left(\frac{x - x_\mu}{2}\right)} 282 | 283 | For better numerical stability the implemented form is instead: 284 | 285 | .. math:: 286 | E(x) = \sum_{\mu=-R}^{R}E(x_\mu)\frac{\mathrm{sinc}\left(\frac{2R+1}{2}(x-x_\mu)\right)}{\mathrm{sinc}\left(\frac{1}{2}(x-x_\mu)\right)} 287 | 288 | #. 10.22331/q-2022-03-30-677, Eq. (57) 289 | #. https://pennylane.ai/qml/demos/tutorial_general_parshift/, 2024-03-14 290 | 291 | Args: 292 | x_vals: List of points to evaluate the function in. 293 | energy_vals: Pre-calculated points of original function. 294 | R: Parameter to control how many points are needed. 295 | 296 | Returns: 297 | Function value in list of points. 298 | """ 299 | e = np.zeros(len(x_vals)) 300 | if isinstance(energy_vals[0], float): 301 | # Single state case 302 | for i, mu in enumerate(list(range(-R, R + 1))): 303 | x_mu = 2 * mu / (2 * R + 1) * np.pi 304 | for j, x in enumerate(x_vals): 305 | e[j] += ( 306 | energy_vals[i] 307 | * np.sinc((2 * R + 1) / 2 * (x - x_mu) / np.pi) 308 | / (np.sinc(1 / 2 * (x - x_mu) / np.pi)) 309 | ) 310 | else: 311 | # State-averaged case 312 | for k in range(len(energy_vals[0])): 313 | for i, mu in enumerate(list(range(-R, R + 1))): 314 | x_mu = 2 * mu / (2 * R + 1) * np.pi 315 | for j, x in enumerate(x_vals): 316 | e[j] += ( 317 | energy_vals[i][k] # type: ignore 318 | * np.sinc((2 * R + 1) / 2 * (x - x_mu) / np.pi) 319 | / (np.sinc(1 / 2 * (x - x_mu) / np.pi)) 320 | ) 321 | e = e / len(energy_vals) 322 | return e 323 | -------------------------------------------------------------------------------- /tests/test_hartreefock.py: -------------------------------------------------------------------------------- 1 | import slowquant.SlowQuant as sq 2 | 3 | 4 | def test_restricted_hartree_fock_h2o_sto3g() -> None: 5 | """Test restricted Hartree-Fock for H2O with STO-3G. 6 | 7 | Reference data: https://github.com/CrawfordGroup/ProgrammingProjects/tree/master/Project%2303 8 | """ 9 | A = sq.SlowQuant() 10 | A.set_molecule( 11 | """O 0.000000000000 -0.143225816552 0.000000000000; 12 | H 1.638036840407 1.136548822547 -0.000000000000; 13 | H -1.638036840407 1.136548822547 -0.000000000000;""", 14 | distance_unit="bohr", 15 | ) 16 | A.set_basis_set("sto-3g") 17 | A.init_hartree_fock() 18 | A.hartree_fock.run_restricted_hartree_fock() 19 | assert abs(A.molecule.nuclear_repulsion - 8.0023670618) < 10**-6 20 | assert abs(A.hartree_fock.E_hf - (-82.944446990003)) < 10**-6 21 | 22 | 23 | def test_restricted_hartree_fock_h2o_dz() -> None: 24 | """Test restricted Hartree-Fock for H2O with DZ. 25 | 26 | Reference data: https://github.com/CrawfordGroup/ProgrammingProjects/tree/master/Project%2303 27 | """ 28 | A = sq.SlowQuant() 29 | A.set_molecule( 30 | """O 0.000000000000 -0.143225816552 0.000000000000; 31 | H 1.638036840407 1.136548822547 -0.000000000000; 32 | H -1.638036840407 1.136548822547 -0.000000000000;""", 33 | distance_unit="bohr", 34 | ) 35 | A.set_basis_set("dz") 36 | A.init_hartree_fock() 37 | A.hartree_fock.run_restricted_hartree_fock() 38 | assert abs(A.molecule.nuclear_repulsion - 8.0023670618) < 10**-6 39 | assert abs(A.hartree_fock.E_hf - (-83.980246037187)) < 10**-6 40 | 41 | 42 | def test_restricted_hartree_fock_h2o_dzp() -> None: 43 | """Test restricted Hartree-Fock for H2O with DZP. 44 | 45 | Does NOT give the same as in the reference. (Reference is: -84.011188854711) 46 | Reference data: https://github.com/CrawfordGroup/ProgrammingProjects/tree/master/Project%2303 47 | """ 48 | A = sq.SlowQuant() 49 | A.set_molecule( 50 | """O 0.000000000000 -0.143225816552 0.000000000000; 51 | H 1.638036840407 1.136548822547 -0.000000000000; 52 | H -1.638036840407 1.136548822547 -0.000000000000;""", 53 | distance_unit="bohr", 54 | ) 55 | A.set_basis_set("dzp") 56 | A.init_hartree_fock() 57 | A.hartree_fock.run_restricted_hartree_fock() 58 | assert abs(A.molecule.nuclear_repulsion - 8.0023670618) < 10**-6 59 | assert abs(A.hartree_fock.E_hf - (-84.01054766778844)) < 10**-6 60 | 61 | 62 | def test_restricted_hartree_fock_ch4_sto3g() -> None: 63 | """Test restricted Hartree-Fock for CH4 with STO-3G. 64 | 65 | Reference data: https://github.com/CrawfordGroup/ProgrammingProjects/tree/master/Project%2303 66 | """ 67 | A = sq.SlowQuant() 68 | A.set_molecule( 69 | """C -0.000000000000 0.000000000000 0.000000000000; 70 | H 1.183771681898 -1.183771681898 -1.183771681898; 71 | H 1.183771681898 1.183771681898 1.183771681898; 72 | H -1.183771681898 1.183771681898 -1.183771681898; 73 | H -1.183771681898 -1.183771681898 1.183771681898""", 74 | distance_unit="bohr", 75 | ) 76 | A.set_basis_set("sto-3g") 77 | A.init_hartree_fock() 78 | A.hartree_fock.run_restricted_hartree_fock() 79 | assert abs(A.molecule.nuclear_repulsion - 13.4973044620) < 10**-6 80 | assert abs(A.hartree_fock.E_hf - (-53.224154786383)) < 10**-6 81 | -------------------------------------------------------------------------------- /tests/test_integral.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_allclose 3 | 4 | import slowquant.SlowQuant as sq 5 | 6 | 7 | def test_cauchy_schwarz_inequality() -> None: 8 | """Test the use of Cauchy Schwarz inequaility. 9 | 10 | Test that integrals are identical within screening threshold, compared to a reference. 11 | 12 | Test that all integrals are zero if the sceening threshold is huge. 13 | """ 14 | ref = sq.SlowQuant() 15 | ref.set_molecule( 16 | """H 0.000000000000 0.000000000000 0.000000000000; 17 | H 1.000000000000 0.000000000000 0.000000000000; 18 | H 2.000000000000 0.000000000000 0.000000000000; 19 | H 3.000000000000 0.000000000000 0.000000000000; 20 | H 4.000000000000 0.000000000000 0.000000000000; 21 | H 5.000000000000 0.000000000000 0.000000000000""", 22 | distance_unit="bohr", 23 | ) 24 | ref.set_basis_set("dzp") 25 | ref.integral.cauchy_schwarz_threshold = 10**-20 26 | ref_eri = ref.integral.electron_repulsion_tensor 27 | 28 | A = sq.SlowQuant() 29 | A.set_molecule( 30 | """H 0.000000000000 0.000000000000 0.000000000000; 31 | H 1.000000000000 0.000000000000 0.000000000000; 32 | H 2.000000000000 0.000000000000 0.000000000000; 33 | H 3.000000000000 0.000000000000 0.000000000000; 34 | H 4.000000000000 0.000000000000 0.000000000000; 35 | H 5.000000000000 0.000000000000 0.000000000000""", 36 | distance_unit="bohr", 37 | ) 38 | A.set_basis_set("dzp") 39 | A.integral.cauchy_schwarz_threshold = 10**-8 40 | assert_allclose(A.integral.electron_repulsion_tensor, ref_eri, atol=10**-8) 41 | 42 | A = sq.SlowQuant() 43 | A.set_molecule( 44 | """H 0.000000000000 0.000000000000 0.000000000000; 45 | H 1.000000000000 0.000000000000 0.000000000000; 46 | H 2.000000000000 0.000000000000 0.000000000000; 47 | H 3.000000000000 0.000000000000 0.000000000000; 48 | H 4.000000000000 0.000000000000 0.000000000000; 49 | H 5.000000000000 0.000000000000 0.000000000000""", 50 | distance_unit="bohr", 51 | ) 52 | A.set_basis_set("dzp") 53 | A.integral.cauchy_schwarz_threshold = 10**-3 54 | assert_allclose(A.integral.electron_repulsion_tensor, ref_eri, atol=10**-3) 55 | 56 | A = sq.SlowQuant() 57 | A.set_molecule( 58 | """H 0.000000000000 0.000000000000 0.000000000000; 59 | H 1.000000000000 0.000000000000 0.000000000000; 60 | H 2.000000000000 0.000000000000 0.000000000000; 61 | H 3.000000000000 0.000000000000 0.000000000000; 62 | H 4.000000000000 0.000000000000 0.000000000000; 63 | H 5.000000000000 0.000000000000 0.000000000000""", 64 | distance_unit="bohr", 65 | ) 66 | A.set_basis_set("dzp") 67 | A.integral.cauchy_schwarz_threshold = 10**10 68 | assert_allclose(A.integral.electron_repulsion_tensor, np.zeros(np.shape(ref_eri)), atol=10**-8) 69 | -------------------------------------------------------------------------------- /tests/test_properties.py: -------------------------------------------------------------------------------- 1 | import slowquant.SlowQuant as sq 2 | 3 | 4 | def test_properties_ch3_sto3g() -> None: 5 | """Test unrestricted Hartree-Fock for CH3 with STO-3G. 6 | 7 | Reference data: Szabo and Ostlund 8 | """ 9 | A = sq.SlowQuant() 10 | A.set_molecule( 11 | """C 0.000000000000 0.000000000000 0.000000000000; 12 | H 2.039000000000 0.000000000000 0.000000000000; 13 | H -1.019500000000 1.765825798316 0.000000000000; 14 | H -1.019500000000 -1.765825798316 0.000000000000""", 15 | distance_unit="bohr", 16 | ) 17 | A.set_basis_set("sto-3g") 18 | A.init_hartree_fock() 19 | A.hartree_fock.de_threshold = 10**-6 20 | A.hartree_fock.rmsd_threshold = 10**-6 21 | A.hartree_fock.run_unrestricted_hartree_fock() 22 | assert abs(A.hartree_fock.spin_contamination - 0.0152) < 10**-4 23 | 24 | 25 | def test_properties_ch3_431g() -> None: 26 | """Test unrestricted Hartree-Fock for CH3 with 4-31G. 27 | 28 | Reference data: Szabo and Ostlund 29 | """ 30 | A = sq.SlowQuant() 31 | A.set_molecule( 32 | """C 0.000000000000 0.000000000000 0.000000000000; 33 | H 2.039000000000 0.000000000000 0.000000000000; 34 | H -1.019500000000 1.765825798316 0.000000000000; 35 | H -1.019500000000 -1.765825798316 0.000000000000""", 36 | distance_unit="bohr", 37 | ) 38 | A.set_basis_set("4-31g") 39 | A.init_hartree_fock() 40 | A.hartree_fock.de_threshold = 10**-6 41 | A.hartree_fock.rmsd_threshold = 10**-6 42 | A.hartree_fock.run_unrestricted_hartree_fock() 43 | assert abs(A.hartree_fock.spin_contamination - 0.0122) < 10**-4 44 | 45 | 46 | def test_properties_ch3_631gs() -> None: 47 | """Test unrestricted Hartree-Fock for CH3 with 6-31G*. 48 | 49 | Reference data: Szabo and Ostlund 50 | """ 51 | A = sq.SlowQuant() 52 | A.set_molecule( 53 | """C 0.000000000000 0.000000000000 0.000000000000; 54 | H 2.039000000000 0.000000000000 0.000000000000; 55 | H -1.019500000000 1.765825798316 0.000000000000; 56 | H -1.019500000000 -1.765825798316 0.000000000000""", 57 | distance_unit="bohr", 58 | ) 59 | A.set_basis_set("6-31g*") 60 | A.init_hartree_fock() 61 | A.hartree_fock.de_threshold = 10**-7 62 | A.hartree_fock.rmsd_threshold = 10**-7 63 | A.hartree_fock.max_scf_iterations = 200 64 | A.hartree_fock.run_unrestricted_hartree_fock() 65 | assert abs(A.hartree_fock.spin_contamination - 0.0118) < 10**-4 66 | 67 | 68 | def test_properties_ch3_631gss() -> None: 69 | """Test unrestricted Hartree-Fock for CH3 with 6-31G**. 70 | 71 | Reference data: Szabo and Ostlund 72 | """ 73 | A = sq.SlowQuant() 74 | A.set_molecule( 75 | """C 0.000000000000 0.000000000000 0.000000000000; 76 | H 2.039000000000 0.000000000000 0.000000000000; 77 | H -1.019500000000 1.765825798316 0.000000000000; 78 | H -1.019500000000 -1.765825798316 0.000000000000""", 79 | distance_unit="bohr", 80 | ) 81 | A.set_basis_set("6-31g**") 82 | A.init_hartree_fock() 83 | A.hartree_fock.de_threshold = 10**-6 84 | A.hartree_fock.rmsd_threshold = 10**-6 85 | A.hartree_fock.run_unrestricted_hartree_fock() 86 | assert abs(A.hartree_fock.spin_contamination - 0.0114) < 10**-4 87 | -------------------------------------------------------------------------------- /tests/test_unitary_coupled_cluster_all.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | import numpy as np 3 | 4 | import slowquant.SlowQuant as sq 5 | from slowquant.unitary_coupled_cluster.linear_response import ( 6 | allprojected, 7 | allselfconsistent, 8 | allstatetransfer, 9 | naive, 10 | projected, 11 | projected_statetransfer, 12 | selfconsistent, 13 | statetransfer, 14 | ) 15 | from slowquant.unitary_coupled_cluster.ucc_wavefunction import WaveFunctionUCC 16 | 17 | 18 | def test_h2_sto3g_uccsd_lr() -> None: 19 | """Test Linear Response for uccsd with all transform methods.""" 20 | SQobj = sq.SlowQuant() 21 | SQobj.set_molecule( 22 | """H 0.0 0.0 0.0; 23 | H 0.0 0.0 0.7;""", 24 | distance_unit="angstrom", 25 | ) 26 | SQobj.set_basis_set("sto-3g") 27 | SQobj.init_hartree_fock() 28 | SQobj.hartree_fock.run_restricted_hartree_fock() 29 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 30 | g_eri = SQobj.integral.electron_repulsion_tensor 31 | WF = WaveFunctionUCC( 32 | SQobj.molecule.number_electrons, 33 | (2, 2), 34 | SQobj.hartree_fock.mo_coeff, 35 | h_core, 36 | g_eri, 37 | "SD", 38 | ) 39 | WF.run_wf_optimization_1step("SLSQP", False) 40 | # SC 41 | LR = allselfconsistent.LinearResponseUCC( 42 | WF, 43 | excitations="SD", 44 | ) 45 | LR.calc_excitation_energies() 46 | assert abs(LR.excitation_energies[0] - 1.0157376) < 10**-3 47 | assert abs(LR.excitation_energies[1] - 1.71950367) < 10**-3 48 | # ST 49 | LR = allstatetransfer.LinearResponseUCC( # type: ignore [assigment] 50 | WF, 51 | excitations="SD", 52 | ) 53 | LR.calc_excitation_energies() 54 | assert abs(LR.excitation_energies[0] - 1.0157376) < 10**-3 55 | assert abs(LR.excitation_energies[1] - 1.71950367) < 10**-3 56 | 57 | 58 | def test_LiH_atmethods_energies() -> None: 59 | """Test LiH at LR methods.""" 60 | SQobj = sq.SlowQuant() 61 | SQobj.set_molecule( 62 | """Li 0.0 0.0 0.0; 63 | H 1.67 0.0 0.0;""", 64 | distance_unit="angstrom", 65 | ) 66 | SQobj.set_basis_set("sto-3g") 67 | SQobj.init_hartree_fock() 68 | SQobj.hartree_fock.run_restricted_hartree_fock() 69 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 70 | g_eri = SQobj.integral.electron_repulsion_tensor 71 | WF = WaveFunctionUCC( 72 | SQobj.molecule.number_electrons, 73 | (2, 2), 74 | SQobj.hartree_fock.mo_coeff, 75 | h_core, 76 | g_eri, 77 | "SD", 78 | ) 79 | WF.run_wf_optimization_1step("SLSQP", True) 80 | 81 | threshold = 10 ** (-3) 82 | 83 | # atSC 84 | LR_atSC = allselfconsistent.LinearResponseUCC( 85 | WF, 86 | excitations="SD", 87 | ) 88 | LR_atSC.calc_excitation_energies() 89 | 90 | solutions = np.array( 91 | [ 92 | 0.1850601, 93 | 0.24646229, 94 | 0.24646229, 95 | 0.62305832, 96 | 0.85960246, 97 | 2.07742277, 98 | 2.13715343, 99 | 2.13715343, 100 | 2.551118, 101 | ] 102 | ) 103 | 104 | assert np.allclose(LR_atSC.excitation_energies, solutions, atol=threshold) 105 | 106 | # atST 107 | LR_atST = allstatetransfer.LinearResponseUCC( 108 | WF, 109 | excitations="SD", 110 | ) 111 | LR_atST.calc_excitation_energies() 112 | 113 | solutions = np.array( 114 | [ 115 | 0.1851181, 116 | 0.24715136, 117 | 0.24715136, 118 | 0.6230648, 119 | 0.85960395, 120 | 2.07752209, 121 | 2.13720198, 122 | 2.13720198, 123 | 2.55113802, 124 | ] 125 | ) 126 | 127 | assert np.allclose(LR_atST.excitation_energies, solutions, atol=threshold) 128 | 129 | 130 | def test_LiH_naiveq_methods_energies() -> None: 131 | """Test LiH energies for naive q LR methods.""" 132 | SQobj = sq.SlowQuant() 133 | SQobj.set_molecule( 134 | """Li 0.0 0.0 0.0; 135 | H 1.67 0.0 0.0;""", 136 | distance_unit="angstrom", 137 | ) 138 | SQobj.set_basis_set("sto-3g") 139 | SQobj.init_hartree_fock() 140 | SQobj.hartree_fock.run_restricted_hartree_fock() 141 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 142 | g_eri = SQobj.integral.electron_repulsion_tensor 143 | WF = WaveFunctionUCC( 144 | SQobj.molecule.number_electrons, 145 | (2, 2), 146 | SQobj.hartree_fock.mo_coeff, 147 | h_core, 148 | g_eri, 149 | "SD", 150 | ) 151 | WF.run_wf_optimization_1step("SLSQP", True) 152 | 153 | threshold = 10 ** (-5) 154 | 155 | # naive 156 | LR_naive = naive.LinearResponseUCC(WF, excitations="SD") 157 | LR_naive.calc_excitation_energies() 158 | 159 | solutions = np.array( 160 | [ 161 | 0.12957563, 162 | 0.17886086, 163 | 0.17886086, 164 | 0.60514579, 165 | 0.64715988, 166 | 0.74104045, 167 | 0.74104045, 168 | 1.00396876, 169 | 2.0747935, 170 | 2.13715595, 171 | 2.13715595, 172 | 2.45575825, 173 | 2.95516593, 174 | ] 175 | ) 176 | assert np.allclose(LR_naive.excitation_energies, solutions, atol=threshold) 177 | 178 | # proj: 179 | LR_naive = projected.LinearResponseUCC( # type: ignore [assigment] 180 | WF, 181 | excitations="SD", 182 | ) 183 | LR_naive.calc_excitation_energies() 184 | 185 | solutions = np.array( 186 | [ 187 | 0.12957561, 188 | 0.17886086, 189 | 0.17886086, 190 | 0.60514593, 191 | 0.6471598, 192 | 0.74104045, 193 | 0.74104045, 194 | 1.00396873, 195 | 2.0747935, 196 | 2.13715595, 197 | 2.13715595, 198 | 2.45575825, 199 | 2.95516593, 200 | ] 201 | ) 202 | assert np.allclose(LR_naive.excitation_energies, solutions, atol=threshold) 203 | 204 | # SC 205 | LR_naive = selfconsistent.LinearResponseUCC( # type: ignore [assigment] 206 | WF, 207 | excitations="SD", 208 | ) 209 | LR_naive.calc_excitation_energies() 210 | 211 | solutions = np.array( 212 | [ 213 | 0.12957564, 214 | 0.17886086, 215 | 0.17886086, 216 | 0.60514581, 217 | 0.64715989, 218 | 0.74104045, 219 | 0.74104045, 220 | 1.00396876, 221 | 2.0747935, 222 | 2.13715595, 223 | 2.13715595, 224 | 2.45575825, 225 | 2.95516593, 226 | ] 227 | ) 228 | assert np.allclose(LR_naive.excitation_energies, solutions, atol=threshold) 229 | 230 | # ST 231 | LR_naive = statetransfer.LinearResponseUCC( # type: ignore [assigment] 232 | WF, 233 | excitations="SD", 234 | ) 235 | LR_naive.calc_excitation_energies() 236 | 237 | solutions = np.array( 238 | [ 239 | 0.12957561, 240 | 0.17886086, 241 | 0.17886086, 242 | 0.60514593, 243 | 0.64715981, 244 | 0.74104045, 245 | 0.74104045, 246 | 1.00396874, 247 | 2.0747935, 248 | 2.13715595, 249 | 2.13715595, 250 | 2.45575825, 251 | 2.95516593, 252 | ] 253 | ) 254 | assert np.allclose(LR_naive.excitation_energies, solutions, atol=threshold) 255 | 256 | 257 | def test_LiH_naiveq_methods_matrices() -> None: 258 | """Test LiH all matrices and their properties for naive q LR methods.""" 259 | SQobj = sq.SlowQuant() 260 | SQobj.set_molecule( 261 | """Li 0.0 0.0 0.0; 262 | H 1.67 0.0 0.0;""", 263 | distance_unit="angstrom", 264 | ) 265 | SQobj.set_basis_set("sto-3g") 266 | SQobj.init_hartree_fock() 267 | SQobj.hartree_fock.run_restricted_hartree_fock() 268 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 269 | g_eri = SQobj.integral.electron_repulsion_tensor 270 | WF = WaveFunctionUCC( 271 | SQobj.molecule.number_electrons, 272 | (2, 2), 273 | SQobj.hartree_fock.mo_coeff, 274 | h_core, 275 | g_eri, 276 | "SD", 277 | ) 278 | WF.run_wf_optimization_1step("SLSQP", True) 279 | 280 | threshold = 10 ** (-5) 281 | 282 | # naive 283 | LR_naive = naive.LinearResponseUCC( 284 | WF, 285 | excitations="SD", 286 | ) 287 | 288 | assert np.allclose(LR_naive.A, LR_naive.A.T, atol=threshold) 289 | assert np.allclose(LR_naive.B, LR_naive.B.T, atol=threshold) 290 | assert np.allclose(LR_naive.Delta, np.zeros_like(LR_naive.Delta), atol=threshold) 291 | 292 | # SC 293 | LR_naive = selfconsistent.LinearResponseUCC( # type: ignore [assigment] 294 | WF, 295 | excitations="SD", 296 | ) 297 | 298 | assert np.allclose(LR_naive.A, LR_naive.A.T, atol=threshold) 299 | assert np.allclose(LR_naive.B, LR_naive.B.T, atol=threshold) 300 | assert np.allclose(LR_naive.Delta, np.zeros_like(LR_naive.Delta), atol=threshold) 301 | 302 | # projected: 303 | threshold = 10 ** (-5) 304 | LR_naive = projected.LinearResponseUCC( # type: ignore [assigment] 305 | WF, 306 | excitations="SD", 307 | ) 308 | 309 | threshold = 10 ** (-5) 310 | assert np.allclose(LR_naive.A, LR_naive.A.T, atol=threshold) 311 | assert np.allclose(LR_naive.B, LR_naive.B.T, atol=threshold) 312 | assert np.allclose(LR_naive.Delta, np.zeros_like(LR_naive.Delta), atol=threshold) 313 | 314 | # ST 315 | LR_naive = statetransfer.LinearResponseUCC( # type: ignore [assigment] 316 | WF, 317 | excitations="SD", 318 | ) 319 | 320 | assert np.allclose(LR_naive.A, LR_naive.A.T, atol=threshold) 321 | assert np.allclose(LR_naive.B, LR_naive.B.T, atol=threshold) 322 | assert np.allclose(LR_naive.Delta, np.zeros_like(LR_naive.Delta), atol=threshold) 323 | 324 | 325 | def test_LiH_allproj_energies() -> None: 326 | """Test LiH for all-proj LR method.""" 327 | SQobj = sq.SlowQuant() 328 | SQobj.set_molecule( 329 | """Li 0.0 0.0 0.0; 330 | H 1.67 0.0 0.0;""", 331 | distance_unit="angstrom", 332 | ) 333 | SQobj.set_basis_set("sto-3g") 334 | SQobj.init_hartree_fock() 335 | SQobj.hartree_fock.run_restricted_hartree_fock() 336 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 337 | g_eri = SQobj.integral.electron_repulsion_tensor 338 | WF = WaveFunctionUCC( 339 | SQobj.molecule.number_electrons, 340 | (2, 2), 341 | SQobj.hartree_fock.mo_coeff, 342 | h_core, 343 | g_eri, 344 | "SD", 345 | ) 346 | WF.run_wf_optimization_1step("SLSQP", True) 347 | 348 | threshold = 10 ** (-5) 349 | 350 | LR_naive = allprojected.LinearResponseUCC( 351 | WF, 352 | excitations="SD", 353 | ) 354 | LR_naive.calc_excitation_energies() 355 | 356 | solutions = np.array( 357 | [ 358 | 0.12973291, 359 | 0.18092743, 360 | 0.18092743, 361 | 0.60537541, 362 | 0.64747353, 363 | 0.74982411, 364 | 0.74982411, 365 | 1.00424384, 366 | 2.07489665, 367 | 2.13720665, 368 | 2.13720665, 369 | 2.45601484, 370 | 2.95606043, 371 | ] 372 | ) 373 | assert np.allclose(LR_naive.excitation_energies, solutions, atol=threshold) 374 | 375 | 376 | def test_LiH_STproj_energies() -> None: 377 | """Test LiH for ST-proj LR method.""" 378 | SQobj = sq.SlowQuant() 379 | SQobj.set_molecule( 380 | """Li 0.0 0.0 0.0; 381 | H 1.67 0.0 0.0;""", 382 | distance_unit="angstrom", 383 | ) 384 | SQobj.set_basis_set("sto-3g") 385 | SQobj.init_hartree_fock() 386 | SQobj.hartree_fock.run_restricted_hartree_fock() 387 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 388 | g_eri = SQobj.integral.electron_repulsion_tensor 389 | WF = WaveFunctionUCC( 390 | SQobj.molecule.number_electrons, 391 | (2, 2), 392 | SQobj.hartree_fock.mo_coeff, 393 | h_core, 394 | g_eri, 395 | "SD", 396 | ) 397 | WF.run_wf_optimization_1step("SLSQP", True) 398 | 399 | threshold = 10 ** (-5) 400 | 401 | LR_naive = projected_statetransfer.LinearResponseUCC( 402 | WF, 403 | excitations="SD", 404 | ) 405 | LR_naive.calc_excitation_energies() 406 | 407 | solutions = np.array( 408 | [ 409 | 0.12973291, 410 | 0.18092743, 411 | 0.18092743, 412 | 0.60537541, 413 | 0.64747353, 414 | 0.74982411, 415 | 0.74982411, 416 | 1.00424384, 417 | 2.07489665, 418 | 2.13720665, 419 | 2.13720665, 420 | 2.45601484, 421 | 2.95606043, 422 | ] 423 | ) 424 | 425 | assert np.allclose(LR_naive.excitation_energies, solutions, atol=threshold) 426 | -------------------------------------------------------------------------------- /tests/test_unitary_product_state.py: -------------------------------------------------------------------------------- 1 | # type: ignore 2 | import numpy as np 3 | 4 | import slowquant.SlowQuant as sq 5 | import slowquant.unitary_coupled_cluster.linear_response.allstatetransfer as allstlr 6 | import slowquant.unitary_coupled_cluster.linear_response.naive as naivelr 7 | from slowquant.unitary_coupled_cluster.sa_ups_wavefunction import WaveFunctionSAUPS 8 | from slowquant.unitary_coupled_cluster.ucc_wavefunction import WaveFunctionUCC 9 | from slowquant.unitary_coupled_cluster.ups_wavefunction import WaveFunctionUPS 10 | 11 | 12 | def test_ups_naivelr() -> None: 13 | """Test LiH UCCSD(2,2) LR.""" 14 | SQobj = sq.SlowQuant() 15 | SQobj.set_molecule( 16 | """Li 0.0 0.0 0.0; 17 | H 1.6717072740 0.0 0.0;""", 18 | distance_unit="angstrom", 19 | ) 20 | SQobj.set_basis_set("STO-3G") 21 | SQobj.init_hartree_fock() 22 | SQobj.hartree_fock.run_restricted_hartree_fock() 23 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 24 | g_eri = SQobj.integral.electron_repulsion_tensor 25 | WF = WaveFunctionUPS( 26 | SQobj.molecule.number_electrons, 27 | (2, 2), 28 | SQobj.hartree_fock.mo_coeff, 29 | h_core, 30 | g_eri, 31 | "tUPS", 32 | ansatz_options={"n_layers": 1, "skip_last_singles": True}, 33 | include_active_kappa=True, 34 | ) 35 | dipole_integrals = ( 36 | SQobj.integral.get_multipole_matrix([1, 0, 0]), 37 | SQobj.integral.get_multipole_matrix([0, 1, 0]), 38 | SQobj.integral.get_multipole_matrix([0, 0, 1]), 39 | ) 40 | WF.run_wf_optimization_1step("SLSQP", True) 41 | LR = naivelr.LinearResponseUCC(WF, excitations="SD") 42 | LR.calc_excitation_energies() 43 | assert abs(LR.excitation_energies[0] - 0.129476) < 10**-4 44 | assert abs(LR.excitation_energies[1] - 0.178749) < 10**-4 45 | assert abs(LR.excitation_energies[2] - 0.178749) < 10**-4 46 | assert abs(LR.excitation_energies[3] - 0.604681) < 10**-4 47 | assert abs(LR.excitation_energies[4] - 0.646707) < 10**-4 48 | assert abs(LR.excitation_energies[5] - 0.740632) < 10**-4 49 | assert abs(LR.excitation_energies[6] - 0.740632) < 10**-4 50 | assert abs(LR.excitation_energies[7] - 1.002914) < 10**-4 51 | assert abs(LR.excitation_energies[8] - 2.074822) < 10**-4 52 | assert abs(LR.excitation_energies[9] - 2.137193) < 10**-4 53 | assert abs(LR.excitation_energies[10] - 2.137193) < 10**-4 54 | assert abs(LR.excitation_energies[11] - 2.455191) < 10**-4 55 | assert abs(LR.excitation_energies[12] - 2.954372) < 10**-4 56 | osc_strengths = LR.get_oscillator_strength(dipole_integrals) 57 | assert abs(osc_strengths[0] - 0.049920) < 10**-3 58 | assert abs(osc_strengths[1] - 0.241184) < 10**-3 59 | assert abs(osc_strengths[2] - 0.241184) < 10**-3 60 | assert abs(osc_strengths[3] - 0.158045) < 10**-3 61 | assert abs(osc_strengths[4] - 0.166539) < 10**-3 62 | assert abs(osc_strengths[5] - 0.010379) < 10**-3 63 | assert abs(osc_strengths[6] - 0.010379) < 10**-3 64 | assert abs(osc_strengths[7] - 0.006256) < 10**-3 65 | assert abs(osc_strengths[8] - 0.062386) < 10**-3 66 | assert abs(osc_strengths[9] - 0.128862) < 10**-3 67 | assert abs(osc_strengths[10] - 0.128862) < 10**-3 68 | assert abs(osc_strengths[11] - 0.046007) < 10**-3 69 | assert abs(osc_strengths[12] - 0.003904) < 10**-3 70 | 71 | 72 | def test_LiH_sto3g_allST(): 73 | """Test LiH STO-3G all-statetransfer LR oscialltor strength.""" 74 | # Slowquant Object with parameters and setup 75 | SQobj = sq.SlowQuant() 76 | SQobj.set_molecule( 77 | """Li 0.0 0.0 0.0; 78 | H 1.67 0.0 0.0;""", 79 | distance_unit="angstrom", 80 | ) 81 | SQobj.set_basis_set("sto-3g") 82 | # HF 83 | SQobj.init_hartree_fock() 84 | SQobj.hartree_fock.run_restricted_hartree_fock() 85 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 86 | g_eri = SQobj.integral.electron_repulsion_tensor 87 | WF = WaveFunctionUCC( 88 | SQobj.molecule.number_electrons, 89 | (2, 2), 90 | SQobj.hartree_fock.mo_coeff, 91 | h_core, 92 | g_eri, 93 | "SD", 94 | ) 95 | WF.run_wf_optimization_1step("SLSQP", True) 96 | WF2 = WaveFunctionUPS( 97 | SQobj.molecule.number_electrons, 98 | (2, 2), 99 | WF.c_mo, 100 | h_core, 101 | g_eri, 102 | "tUPS", 103 | ansatz_options={"n_layers": 1}, 104 | ) 105 | WF2.run_wf_optimization_1step("SLSQP", False) 106 | # Linear Response 107 | LR = allstlr.LinearResponseUCC( 108 | WF2, 109 | excitations="SD", 110 | ) 111 | LR.calc_excitation_energies() 112 | 113 | thresh = 10**-3 114 | 115 | # Check excitation energies 116 | solutions = np.array( 117 | [ 118 | 0.1851181, 119 | 0.24715136, 120 | 0.24715136, 121 | 0.6230648, 122 | 0.85960395, 123 | 2.07752209, 124 | 2.13720198, 125 | 2.13720198, 126 | 2.55113802, 127 | ] 128 | ) 129 | 130 | assert np.allclose(LR.excitation_energies, solutions, atol=thresh) 131 | 132 | # Calculate dipole integrals 133 | dipole_integrals = ( 134 | SQobj.integral.get_multipole_matrix([1, 0, 0]), 135 | SQobj.integral.get_multipole_matrix([0, 1, 0]), 136 | SQobj.integral.get_multipole_matrix([0, 0, 1]), 137 | ) 138 | 139 | # Get oscillator strength for each excited state 140 | osc_strengths = LR.get_oscillator_strength(dipole_integrals) 141 | assert abs(osc_strengths[0] - 0.06668878) < thresh 142 | assert abs(osc_strengths[1] - 0.33360367) < thresh 143 | assert abs(osc_strengths[2] - 0.33360367) < thresh 144 | assert abs(osc_strengths[3] - 0.30588158) < thresh 145 | assert abs(osc_strengths[4] - 0.02569977) < thresh 146 | assert abs(osc_strengths[5] - 0.06690658) < thresh 147 | assert abs(osc_strengths[6] - 0.13411942) < thresh 148 | assert abs(osc_strengths[7] - 0.13411942) < thresh 149 | assert abs(osc_strengths[8] - 0.04689274) < thresh 150 | 151 | 152 | def test_ups_water_44() -> None: 153 | """Test a larger active space.""" 154 | SQobj = sq.SlowQuant() 155 | SQobj.set_molecule( 156 | """O 0.0 0.0 0.1035174918; 157 | H 0.0 0.7955612117 -0.4640237459; 158 | H 0.0 -0.7955612117 -0.4640237459;""", 159 | distance_unit="angstrom", 160 | ) 161 | SQobj.set_basis_set("STO-3G") 162 | SQobj.init_hartree_fock() 163 | SQobj.hartree_fock.run_restricted_hartree_fock() 164 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 165 | g_eri = SQobj.integral.electron_repulsion_tensor 166 | WF = WaveFunctionUPS( 167 | SQobj.molecule.number_electrons, 168 | (4, 4), 169 | SQobj.hartree_fock.mo_coeff, 170 | h_core, 171 | g_eri, 172 | "fUCCSD", 173 | ansatz_options={}, 174 | include_active_kappa=True, 175 | ) 176 | WF.run_wf_optimization_1step("SLSQP", True) 177 | assert abs(WF.energy_elec - -83.97256228053688) < 10**-8 178 | 179 | 180 | def test_saups_h2_3states() -> None: 181 | """This should give exactly the same as FCI. 182 | 183 | Since all states, are includes in the subspace expansion. 184 | """ 185 | SQobj = sq.SlowQuant() 186 | SQobj.set_molecule( 187 | """H 0.0 0.0 0.0; 188 | H 0.0 0.0 0.735;""", 189 | distance_unit="angstrom", 190 | ) 191 | SQobj.set_basis_set("STO-3G") 192 | SQobj.init_hartree_fock() 193 | SQobj.hartree_fock.run_restricted_hartree_fock() 194 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 195 | g_eri = SQobj.integral.electron_repulsion_tensor 196 | 197 | WF = WaveFunctionSAUPS( 198 | SQobj.molecule.number_electrons, 199 | (2, 2), 200 | SQobj.hartree_fock.mo_coeff, 201 | h_core, 202 | g_eri, 203 | ( 204 | [ 205 | [1], 206 | [2 ** (-1 / 2), -(2 ** (-1 / 2))], 207 | [1], 208 | ], 209 | [ 210 | ["1100"], 211 | ["1001", "0110"], 212 | ["0011"], 213 | ], 214 | ), 215 | "tUPS", 216 | ansatz_options={"n_layers": 1, "skip_last_singles": True}, 217 | include_active_kappa=True, 218 | ) 219 | 220 | WF.run_wf_optimization_1step("SLSQP", True) 221 | 222 | dipole_integrals = ( 223 | SQobj.integral.get_multipole_matrix([1, 0, 0]), 224 | SQobj.integral.get_multipole_matrix([0, 1, 0]), 225 | SQobj.integral.get_multipole_matrix([0, 0, 1]), 226 | ) 227 | 228 | assert abs(WF.excitation_energies[0] - 0.974553) < 10**-6 229 | assert abs(WF.excitation_energies[1] - 1.632364) < 10**-6 230 | osc = WF.get_oscillator_strenghts(dipole_integrals) 231 | assert abs(osc[0] - 0.8706) < 10**-3 232 | assert abs(osc[1] - 0.0) < 10**-3 233 | 234 | 235 | def test_saups_h3_3states() -> None: 236 | """Test a system where the subspace is not everything.""" 237 | SQobj = sq.SlowQuant() 238 | SQobj.set_molecule( 239 | """H -0.45 -0.3897114317 0.0; 240 | H 0.45 -0.3897114317 0.0; 241 | H 0.0 0.3897114317 0.0;""", 242 | distance_unit="angstrom", 243 | molecular_charge=1, 244 | ) 245 | SQobj.set_basis_set("STO-3G") 246 | SQobj.init_hartree_fock() 247 | SQobj.hartree_fock.run_restricted_hartree_fock() 248 | h_core = SQobj.integral.kinetic_energy_matrix + SQobj.integral.nuclear_attraction_matrix 249 | g_eri = SQobj.integral.electron_repulsion_tensor 250 | 251 | WF = WaveFunctionSAUPS( 252 | SQobj.molecule.number_electrons, 253 | (2, 3), 254 | SQobj.hartree_fock.mo_coeff, 255 | h_core, 256 | g_eri, 257 | ( 258 | [ 259 | [1], 260 | [2 ** (-1 / 2), -(2 ** (-1 / 2))], 261 | [2 ** (-1 / 2), -(2 ** (-1 / 2))], 262 | ], 263 | [ 264 | ["110000"], 265 | ["100100", "011000"], 266 | ["100001", "010010"], 267 | ], 268 | ), 269 | "tUPS", 270 | ansatz_options={"n_layers": 2, "skip_last_singles": True}, 271 | include_active_kappa=True, 272 | ) 273 | 274 | WF.run_wf_optimization_2step("BFGS", True) 275 | 276 | dipole_integrals = ( 277 | SQobj.integral.get_multipole_matrix([1, 0, 0]), 278 | SQobj.integral.get_multipole_matrix([0, 1, 0]), 279 | SQobj.integral.get_multipole_matrix([0, 0, 1]), 280 | ) 281 | 282 | assert abs(WF.excitation_energies[0] - 0.838466) < 10**-6 283 | assert abs(WF.excitation_energies[1] - 0.838466) < 10**-6 284 | osc = WF.get_oscillator_strenghts(dipole_integrals) 285 | assert abs(osc[0] - 0.7569) < 10**-3 286 | assert abs(osc[1] - 0.7569) < 10**-3 287 | -------------------------------------------------------------------------------- /tests/test_unrestrictedhartreefock.py: -------------------------------------------------------------------------------- 1 | import slowquant.SlowQuant as sq 2 | 3 | 4 | def test_unrestricted_hartree_fock_h2o_sto3g() -> None: 5 | """Test unrestricted Hartree-Fock for H2O with STO-3G. 6 | 7 | Should give the exact same as restricted Hartree-Fock 8 | when orbitals are not forced to break symmetry. 9 | """ 10 | A = sq.SlowQuant() 11 | A.set_molecule( 12 | """O 0.000000000000 -0.143225816552 0.000000000000; 13 | H 1.638036840407 1.136548822547 -0.000000000000; 14 | H -1.638036840407 1.136548822547 -0.000000000000;""", 15 | distance_unit="bohr", 16 | ) 17 | A.set_basis_set("sto-3g") 18 | A.init_hartree_fock() 19 | A.hartree_fock.run_restricted_hartree_fock() 20 | A.hartree_fock.uhf_lumo_homo_mix_coeff = 0.0 21 | A.hartree_fock.run_unrestricted_hartree_fock() 22 | assert abs(A.hartree_fock.E_hf - A.hartree_fock.E_uhf) < 10**-6 23 | 24 | 25 | def test_unrestricted_hartree_fock_n2_631gs() -> None: 26 | """Test unresticted Hartree-Fock N2 with 6-31G*. 27 | 28 | Does NOT give the same as the reference. (Reference for N2: -108.94235) 29 | Reference: Szabo and Ostlund 30 | """ 31 | A = sq.SlowQuant() 32 | A.set_molecule( 33 | """N 0.000000000000 0.000000000000 0.000000000000; 34 | N 2.074000000000 0.000000000000 -0.000000000000""", 35 | distance_unit="bohr", 36 | ) 37 | A.set_basis_set("6-31G*") 38 | A.init_hartree_fock() 39 | A.hartree_fock.run_unrestricted_hartree_fock() 40 | A.hartree_fock.run_restricted_hartree_fock() 41 | assert abs(A.hartree_fock.E_uhf + A.molecule.nuclear_repulsion - (-108.942686389118)) < 10**-6 42 | # Check RHF and UHF gives the same for closed shell case. 43 | assert abs(A.hartree_fock.E_hf - A.hartree_fock.E_uhf) < 10**-6 44 | 45 | A = sq.SlowQuant() 46 | A.set_molecule( 47 | """N 0.000000000000 0.000000000000 0.000000000000; 48 | N 2.074000000000 0.000000000000 -0.000000000000""", 49 | distance_unit="bohr", 50 | ) 51 | A.set_basis_set("6-31G*") 52 | A.molecule.molecular_charge = 1 53 | A.init_hartree_fock() 54 | A.hartree_fock.run_unrestricted_hartree_fock() 55 | assert abs(A.hartree_fock.E_uhf + A.molecule.nuclear_repulsion - (-108.36597)) < 10**-5 56 | 57 | 58 | def test_unrestricted_hartree_fock_li_631gss() -> None: 59 | """Test unresticted Hartree-Fock Li with 6-31G**. 60 | 61 | Reference: https://github.com/rpmuller/pyquante2/blob/master/pyquante2/scf/hamiltonians.py 62 | """ 63 | A = sq.SlowQuant() 64 | A.set_molecule("""Li 0.000000000000 0.000000000000 0.000000000000;""", distance_unit="bohr") 65 | A.set_basis_set("6-31G**") 66 | A.init_hartree_fock() 67 | A.hartree_fock.run_unrestricted_hartree_fock() 68 | A.hartree_fock.run_restricted_hartree_fock() 69 | assert abs(A.hartree_fock.E_uhf - (-7.4313707537)) < 10**-5 70 | --------------------------------------------------------------------------------