├── .gitignore
├── .readthedocs.yaml
├── CITATION.cff
├── DFT_ref.bib
├── DFT_worksheet.pdf
├── LICENSE
├── README.md
├── docs
├── Makefile
├── requirements.txt
└── source
│ ├── DFT_ref.bib
│ ├── NB1_functions.rst
│ ├── NB2_functions.rst
│ ├── NB3_functions.rst
│ ├── citation.rst
│ ├── conf.py
│ ├── figures
│ ├── 1D_hartree.png
│ ├── 321_wavefunction.png
│ ├── 512_prob_den.png
│ ├── GGA_exch_plot.png
│ ├── LDA_cor.png
│ ├── LDA_exch.png
│ ├── arrow.png
│ ├── connect.png
│ ├── connected.png
│ ├── copy_drive_location.png
│ ├── copy_to_drive.png
│ ├── den_compare_blue.png
│ ├── density_comparison.png
│ ├── grid_density.png
│ ├── heptacene.png
│ ├── heptacene_HOMO3.png
│ ├── heptacene_diagram.png
│ ├── heptacene_scf.png
│ ├── runtime1.png
│ ├── runtime2.png
│ ├── show_code.png
│ ├── show_code_anim.gif
│ ├── show_code_button.png
│ └── wavefunction_anim.gif
│ ├── index.rst
│ ├── other_resources.rst
│ └── running_code.rst
├── figures
├── NB1_wavefunction.png
├── NB2_anthracene.png
├── NB3_density.png
└── graphical_abstract.png
├── notebooks
├── NB1_3D_PIB.ipynb
├── NB2_PAH_HF.ipynb
├── NB3_DFT_PIB.ipynb
├── NB3_DFT_PIB_calculator.ipynb
├── notebook_functions
│ ├── NB1_functions.py
│ ├── NB2_functions.py
│ ├── NB3_functions.py
│ └── __init__.py
├── old_NB3_DFT_PIB.ipynb
└── old_NB3_DFT_PIB_calculator.ipynb
└── offline_jupyter
├── README.md
├── offline_NB1_3D_PIB.ipynb
├── offline_NB2_PAH_HF.ipynb
├── offline_NB3_DFT_PIB.ipynb
├── old_ipv_compatible
├── info.txt
├── offline_NB1_3D_PIB.ipynb
├── offline_NB2_PAH_HF.ipynb
├── offline_NB3_DFT_PIB.ipynb
├── offline_NB3_DFT_PIB_calculator.ipynb
└── requirements.txt
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Sphinx Documentation
2 | docs/build/
3 |
4 | # Python
5 | notebooks/notebook_functions/__pycache__
6 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Set the version of Python and other tools you might need
9 | build:
10 | os: ubuntu-22.04
11 | tools:
12 | python: "3.12"
13 |
14 | # Build documentation in the docs/ directory with Sphinx
15 | sphinx:
16 | configuration: docs/source/conf.py
17 |
18 | # We recommend specifying your dependencies to enable reproducible builds:
19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
20 | python:
21 | install:
22 | - requirements: docs/requirements.txt
23 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | title: Opening the Density Functional Theory Black Box
3 | type: software
4 | authors:
5 | - given-names: Jacob S.
6 | family-names: Hirschi
7 | affiliation: Oregon State University
8 | email: hirschij@oregonstate.edu
9 | orcid: 'https://orcid.org/0009-0004-3327-8671'
10 | - given-names: Dayana
11 | family-names: Bashirova
12 | affiliation: Oregon State University
13 | orcid: 'https://orcid.org/0009-0006-2283-717X'
14 | - given-names: Tim J.
15 | family-names: Zuehsldorff
16 | affiliation: Oregon State University
17 | orcid: 'https://orcid.org/0000-0002-6960-756X'
18 | identifiers:
19 | - type: doi
20 | value: 10.1021/acs.jchemed.3c00535
21 | repository-code: 'https://github.com/tjz21/DFT_PIB_Code'
22 | abstract: >-
23 | Interactive Jupyter Notebooks for learning the
24 | fundamentals of Density-Functional Theory (DFT).
25 | keywords:
26 | - quantum chemistry
27 | - density-functional theory
28 | - computational chemistry
29 | - open educational resource
30 | - electronic structure
31 | license: MIT
32 | date-released: '2023-10-16'
33 | preferred-citation:
34 | authors:
35 | - given-names: Jacob S.
36 | family-names: Hirschi
37 | affiliation: Oregon State University
38 | email: hirschij@oregonstate.edu
39 | orcid: 'https://orcid.org/0009-0004-3327-8671'
40 | - given-names: Dayana
41 | family-names: Bashirova
42 | affiliation: Oregon State University
43 | orcid: 'https://orcid.org/0009-0006-2283-717X'
44 | - given-names: Tim J.
45 | family-names: Zuehsldorff
46 | affiliation: Oregon State University
47 | orcid: 'https://orcid.org/0000-0002-6960-756X'
48 | date-published: 2023-10-16
49 | doi: 10.1021/acs.jchemed.3c00535
50 | issue: 11
51 | journal: Journal of Chemical Education
52 | publisher:
53 | name: American Chemical Society
54 | start: 4496
55 | title: "Opening the Density Functional Theory Black
56 | Box: A Collection of Pedagogic Jupyter Notebooks"
57 | type: article
58 | url: "https://pubs.acs.org/doi/10.1021/acs.jchemed.3c00535"
59 | volume: 100
60 | title: "Opening the Density Functional Theory Black
61 | Box: A Collection of Pedagogic Jupyter Notebooks"
62 |
--------------------------------------------------------------------------------
/DFT_ref.bib:
--------------------------------------------------------------------------------
1 | @article{DFTBlackBox,
2 | author = {Hirschi, Jacob S. and Bashirova, Dayana and Zuehlsdorff, Tim J.},
3 | title = {Opening the Density Functional Theory Black Box: A Collection of Pedagogic Jupyter Notebooks},
4 | journal = {J. Chem. Educ.},
5 | volume = {100},
6 | number = {11},
7 | pages = {4496-4503},
8 | year = {2023},
9 | doi = {10.1021/acs.jchemed.3c00535},
10 | URL = {https://doi.org/10.1021/acs.jchemed.3c00535},
11 | }
12 |
--------------------------------------------------------------------------------
/DFT_worksheet.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/DFT_worksheet.pdf
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-2024 Tim J. Zuehlsdorff
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Opening the Density-Functional Theory Black Box
2 |
3 |
4 |
5 | ---
6 |
7 | > 'It is nice to know that the computer understands the problem. But I would like to understand it too.'
8 | >
9 | > \- **Eugene Wigner**
10 |
11 | ---
12 |
13 | [](https://dft-pib-code.readthedocs.io/en/latest/?badge=latest)
14 | [](https://www.python.org/)
15 | [](https://opensource.org/licenses/MIT)
16 | [](https://github.com/tjz21/DFT_PIB_Code/releases/latest)
17 | [](http://dx.doi.org/10.26434/chemrxiv-2023-jfgcl)
18 | [](http://dx.doi.org/10.1021/acs.jchemed.3c00535)
19 |
20 | This repository contains three Google Colab notebooks that are designed to facilitate understanding of Density-Functional Theory (DFT) through interactive visualizations. Our motivation for developing this software stems from the knowledge deficiency that is often produced from using DFT as a black box in commercial software. By applying DFT to the familiar particle in a box model system employing a real-space grid basis, we hope to have reduced DFT to its fundamental essence fit for pedagogy. Brief instructions for executing the code are provided at the beginning of each notebook and a [problem sheet](https://github.com/tjz21/DFT_PIB_Code/blob/main/DFT_worksheet.pdf) for getting started is attached. The notebooks can be accessed without any installation through Google Colab by simply clicking on the links and signing in with a Google account (offline alternative is provided [here](offline_jupyter/README.md)). Python programming knowledge is not required, and docs are hosted through [ReadTheDocs](https://dft-pib-code.readthedocs.io/en/latest/).
21 |
22 |
23 |
24 |
25 |
26 |
27 | ## Notebook 1–Particle in a 3D Box
28 |
29 | In this notebook, we’ll consider the particle in a three-dimensional box system treated in any undergraduate physical chemistry textbook. High-quality energy level diagrams and isosurface renderings of the wavefunction can be generated from user-specified box lengths. Depicted here is the 321 state of an anthracene-like box of dimensions 16 x 8 x 3 Bohr.
30 |
31 |
32 | Click here to open the notebook in Google Colab:
33 |
34 |
35 |
36 | [](https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB1_3D_PIB.ipynb)
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ## Notebook 2–PAH Frontier Orbitals
46 |
47 | Next, we’ll look at a real chemical system in the form of polycyclic aromatic hydrocarbons (PAHs). We can perform Hartree-Fock/STO-3G calculations to find the shapes and energies of their frontier molecular orbitals, which can make for interesting comparisons with the analogous results from Notebook 1.
48 |
49 |
50 |
51 | [](https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB2_PAH_HF.ipynb)
52 |
53 |
54 |
55 |
56 | ## Notebook 3–Density-Functional Theory
57 |
58 | Finally, we’ll reconsider the system from Notebook 1, but now we’ll turn on electron-electron interaction through the Kohn-Sham potential. We’ll consider each term of the single-particle Hamiltonian and put everything together into a self-consistent field (SCF) DFT calculation. We can then analyze the how the density and eigeneneriges change as a function of SCF iteration number. LDA and PBE are the available exchange-correlation functionals.
59 |
60 | Full theory notebook:
61 |
62 | [](https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB3_DFT_PIB.ipynb)
63 |
64 |
65 | Abbreviated notebook with the DFT calculator and analysis tools:
66 |
67 | [](https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB3_DFT_PIB_calculator.ipynb)
68 |
69 |
70 |
71 | ---
72 |
73 | ### Citation
74 | If you would like to cite this work, please refer to the following publication ([BibTeX](DFT_ref.bib)):
75 |
76 | > Hirschi, J. S.; Bashirova, D.; Zuehlsdorff, T. J.
77 | > Opening the Density Functional Theory Black Box: A Collection of Pedagogic Jupyter Notebooks.
78 | > *J. Chem. Educ.*
79 | > **2023**,
80 | > *100* (11), 4496-4503. https://doi.org/10.1021/acs.jchemed.3c00535
81 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinxcontrib-images
2 | sphinx-rtd-theme
3 | sphinx_design
4 |
--------------------------------------------------------------------------------
/docs/source/DFT_ref.bib:
--------------------------------------------------------------------------------
1 | @article{DFTBlackBox,
2 | author = {Hirschi, Jacob S. and Bashirova, Dayana and Zuehlsdorff, Tim J.},
3 | title = {Opening the Density Functional Theory Black Box: A Collection of Pedagogic Jupyter Notebooks},
4 | journal = {J. Chem. Educ.},
5 | volume = {100},
6 | number = {11},
7 | pages = {4496-4503},
8 | year = {2023},
9 | doi = {10.1021/acs.jchemed.3c00535},
10 | URL = {https://doi.org/10.1021/acs.jchemed.3c00535},
11 | }
12 |
--------------------------------------------------------------------------------
/docs/source/NB1_functions.rst:
--------------------------------------------------------------------------------
1 | Notebook 1 Functions
2 | ====================
3 |
4 | Below are the functions pertaining to the analytic solutions to the 3D PIB system used in `Notebook 1 `_.
5 |
6 |
7 |
8 | .. autosummary::
9 |
10 | NB1_functions.psi_reg
11 | NB1_functions.psi_ener
12 | NB1_functions.markdown_wvfn
13 | NB1_functions.markdown_ener
14 | NB1_functions.isoplotter
15 |
16 | .. automodule:: NB1_functions
17 | :members:
18 | :member-order: bysource
19 |
--------------------------------------------------------------------------------
/docs/source/NB2_functions.rst:
--------------------------------------------------------------------------------
1 | Notebook 2 Functions
2 | ====================
3 |
4 | Below are the functions used for the geometry optimization, visualization, and electronic structure calculation of PAH molecules featured in `Notebook 2 `_.
5 |
6 |
7 |
8 | .. autosummary::
9 |
10 | NB2_functions.FF_optimization
11 | NB2_functions.run_calculation
12 | NB2_functions.get_HL_gap
13 | NB2_functions.elec_properties
14 | NB2_functions.mo_diagram
15 | NB2_functions.draw_orbital
16 |
17 | .. automodule:: NB2_functions
18 | :members:
19 | :member-order: bysource
20 |
--------------------------------------------------------------------------------
/docs/source/NB3_functions.rst:
--------------------------------------------------------------------------------
1 | Notebook 3 Functions
2 | ====================
3 |
4 | See below for the functions needed to build the visualizations and DFT calculator in `Notebook 3 `_.
5 |
6 | .. autosummary::
7 |
8 | NB3_functions.edge_cleaner
9 | NB3_functions.integ_3d
10 | NB3_functions.norm_psi_and_den
11 | NB3_functions.noninter_kin_e
12 | NB3_functions.grid_density
13 | NB3_functions.exch_equation
14 | NB3_functions.exch_pot_eq
15 | NB3_functions.LDA_c_display
16 | NB3_functions.GGAx_pot_eq
17 | NB3_functions.hartree_plotter
18 | NB3_functions.hamiltonian_display
19 | NB3_functions.energy_plot
20 | NB3_functions.hard_walls
21 | NB3_functions.hartree
22 | NB3_functions.lda_exchange
23 | NB3_functions.lda_correlation
24 | NB3_functions.RDG
25 | NB3_functions.pbe_exchange
26 | NB3_functions.cor_den_grad
27 | NB3_functions.pbe_correlation
28 |
29 | .. automodule:: NB3_functions
30 | :members:
31 | :member-order: bysource
32 |
--------------------------------------------------------------------------------
/docs/source/citation.rst:
--------------------------------------------------------------------------------
1 | Citation
2 | ========
3 | This work was published in the `*Journal of Chemical Education* `_.
4 |
5 | :Download .bib: :download:`bibtex`
6 |
7 | ::
8 |
9 | @article{DFTBlackBox,
10 | author = {Hirschi, Jacob S. and Bashirova, Dayana and Zuehlsdorff, Tim J.},
11 | title = {Opening the Density Functional Theory Black Box: A Collection of Pedagogic Jupyter Notebooks},
12 | journal = {J. Chem. Educ.},
13 | volume = {100},
14 | number = {11},
15 | pages = {4496-4503},
16 | year = {2023},
17 | doi = {10.1021/acs.jchemed.3c00535},
18 | URL = {https://doi.org/10.1021/acs.jchemed.3c00535}
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # For the full list of built-in configuration values, see the documentation:
4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
5 |
6 | # -- Project information -----------------------------------------------------
7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8 |
9 | import os
10 | import sys
11 | sys.path.insert(0, os.path.abspath('../../notebooks/notebook_functions'))
12 |
13 | project = 'DFT-PIB Docs'
14 | copyright = '2024, Zuehlsdorff Group'
15 | author = 'Zuehlsdorff Group'
16 | html_scaled_image_link = False
17 |
18 |
19 | # -- General configuration ---------------------------------------------------
20 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
21 |
22 | extensions = [ 'sphinx.ext.autodoc',
23 | 'sphinx.ext.doctest',
24 | 'sphinx.ext.autosummary',
25 | 'sphinx.ext.intersphinx',
26 | 'sphinx_design',
27 | #'sphinxcontrib.images', # disabled until they fix it
28 | ]
29 |
30 | templates_path = ['_templates']
31 | exclude_patterns = []
32 |
33 |
34 |
35 | # -- Options for HTML output -------------------------------------------------
36 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
37 |
38 | html_theme = 'sphinx_rtd_theme'
39 | html_static_path = ['_static']
40 | html_logo = 'figures/den_compare_blue.png'
41 |
--------------------------------------------------------------------------------
/docs/source/figures/1D_hartree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/1D_hartree.png
--------------------------------------------------------------------------------
/docs/source/figures/321_wavefunction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/321_wavefunction.png
--------------------------------------------------------------------------------
/docs/source/figures/512_prob_den.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/512_prob_den.png
--------------------------------------------------------------------------------
/docs/source/figures/GGA_exch_plot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/GGA_exch_plot.png
--------------------------------------------------------------------------------
/docs/source/figures/LDA_cor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/LDA_cor.png
--------------------------------------------------------------------------------
/docs/source/figures/LDA_exch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/LDA_exch.png
--------------------------------------------------------------------------------
/docs/source/figures/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/arrow.png
--------------------------------------------------------------------------------
/docs/source/figures/connect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/connect.png
--------------------------------------------------------------------------------
/docs/source/figures/connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/connected.png
--------------------------------------------------------------------------------
/docs/source/figures/copy_drive_location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/copy_drive_location.png
--------------------------------------------------------------------------------
/docs/source/figures/copy_to_drive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/copy_to_drive.png
--------------------------------------------------------------------------------
/docs/source/figures/den_compare_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/den_compare_blue.png
--------------------------------------------------------------------------------
/docs/source/figures/density_comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/density_comparison.png
--------------------------------------------------------------------------------
/docs/source/figures/grid_density.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/grid_density.png
--------------------------------------------------------------------------------
/docs/source/figures/heptacene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/heptacene.png
--------------------------------------------------------------------------------
/docs/source/figures/heptacene_HOMO3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/heptacene_HOMO3.png
--------------------------------------------------------------------------------
/docs/source/figures/heptacene_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/heptacene_diagram.png
--------------------------------------------------------------------------------
/docs/source/figures/heptacene_scf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/heptacene_scf.png
--------------------------------------------------------------------------------
/docs/source/figures/runtime1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/runtime1.png
--------------------------------------------------------------------------------
/docs/source/figures/runtime2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/runtime2.png
--------------------------------------------------------------------------------
/docs/source/figures/show_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/show_code.png
--------------------------------------------------------------------------------
/docs/source/figures/show_code_anim.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/show_code_anim.gif
--------------------------------------------------------------------------------
/docs/source/figures/show_code_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/show_code_button.png
--------------------------------------------------------------------------------
/docs/source/figures/wavefunction_anim.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/docs/source/figures/wavefunction_anim.gif
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Opening the DFT Black Box Documentation
2 | =======================================
3 |
4 | ::
5 |
6 | --------------------------------------------------------------------------------
7 |
8 | ________ ________ _________ ________ ___ ________
9 | |\ ___ \|\ _____\\___ ___\ |\ __ \|\ \|\ __ \
10 | \ \ \_|\ \ \ \__/\|___ \ \_|____________\ \ \|\ \ \ \ \ \|\ /_
11 | \ \ \ \\ \ \ __\ \ \ \|\____________\ \ ____\ \ \ \ __ \
12 | \ \ \_\\ \ \ \_| \ \ \|____________|\ \ \___|\ \ \ \ \|\ \
13 | \ \_______\ \__\ \ \__\ \ \__\ \ \__\ \_______\
14 | \|_______|\|__| \|__| \|__| \|__|\|_______|
15 |
16 | Software developed by Jacob Hirschi, Dayana Bashirova, and Tim Zuehlsdorff
17 | Oregon State University, Corvallis, OR 97331, USA
18 |
19 | --------------------------------------------------------------------------------
20 |
21 |
22 | General Information
23 | -------------------
24 |
25 | .. image:: https://readthedocs.org/projects/dft-pib-code/badge/?version=latest
26 | :target: https://dft-pib-code.readthedocs.io/en/latest/?badge=latest
27 | :alt: Documentation Status
28 |
29 | .. image:: https://img.shields.io/badge/python-3.10-blue.svg
30 | :target: https://www.python.org/
31 | :alt: Python version
32 |
33 | .. image:: https://img.shields.io/badge/License-MIT-yellow.svg
34 | :target: https://opensource.org/licenses/MIT
35 | :alt: License
36 |
37 | .. image:: https://img.shields.io/github/v/release/tjz21/DFT_PIB_Code
38 | :target: https://github.com/tjz21/DFT_PIB_Code/releases/latest
39 | :alt: Code version
40 |
41 | .. image:: http://img.shields.io/badge/ChemRxiv-10.26434/chemrxiv--2023--jfgcl-EEEA62.svg
42 | :target: http://dx.doi.org/10.26434/chemrxiv-2023-jfgcl
43 | :alt: Preprint link
44 |
45 | .. image:: http://img.shields.io/badge/Paper_DOI-10.1021/acs.jchemed.3c00535-blue.svg
46 | :target: http://dx.doi.org/10.1021/acs.jchemed.3c00535
47 | :alt: Paper link
48 |
49 | Welcome to the documentation site for the educational software DFT-PIB, which applies density-functional theory to the 3D particle in a box model system. The three Google Colab Notebooks are accessible from the `GitHub repo `_ with the `problem worksheet `_, and the instructions are mostly self-contained.
50 |
51 | For completeness, we've also attached a :ref:`short guide ` for executing the code here and a page with other great :ref:`learning resources `. Although the code is not really intended to be used as an API, we've included descriptions of the main functions used in each notebook herein with some examples.
52 |
53 | **Have fun learning about DFT!**
54 |
55 | ----
56 |
57 | Content
58 | -------
59 |
60 | .. toctree::
61 | :maxdepth: 1
62 | :caption: Getting Started
63 |
64 | running_code
65 |
66 | .. toctree::
67 | :maxdepth: 1
68 | :caption: API-style docs
69 |
70 | NB1_functions
71 | NB2_functions
72 | NB3_functions
73 |
74 | .. toctree::
75 | :maxdepth: 1
76 | :caption: Other Info/Links
77 |
78 | other_resources
79 | citation
80 | GitHub Repo Link
81 |
--------------------------------------------------------------------------------
/docs/source/other_resources.rst:
--------------------------------------------------------------------------------
1 | .. _other-resources:
2 |
3 | Further Resources
4 | =================
5 |
6 | Here is a curated list of useful pedagogic resources for learning more about DFT and related topics:
7 |
8 | * `eChem `_: Digital textbook addressing advanced topics in electronic structure.
9 | * `DFT by hand `_: Exercises that involve performing DFT calculations by hand on a simplified two-electron He atom system.
10 | * `python_1d_dft `_: GitHub repository with Jupyter Notebooks that considers DFT in the context of a 1D PIB.
11 | * `DFT with Slater functions `_: Jupyter Notebooks that calculate the DFT total energy of H2 with Slater orbitals.
12 | * `DFT MOOC `_: 25-hour online course delivered by Coursera on DFT.
13 | * `The ABC of DFT `_: Textbook pdf by Prof. Kieron Burke with great explanations and pen-and-paper exercises.
14 | * `HF with Excel `_: Microsoft Excel Spreadsheet with VBA Macros that perform HF/STO-nG calculations on H2.
15 | * `Basis sets with Excel `_: Excel sheets with interactive visualizations of gaussian- and slater-type basis sets.
16 |
--------------------------------------------------------------------------------
/docs/source/running_code.rst:
--------------------------------------------------------------------------------
1 | .. _short-guide:
2 |
3 | Running the Code
4 | ================
5 |
6 | Colab Notebook Code Blocks
7 | --------------------------
8 | In order to run the code in the cloud using Colab, you'll need to first connect to a runtime. You can connect by either (1) clicking the |connect| button in the upper right or (2) trying to run any cell by clicking the |arrow| button alongside it:
9 |
10 | .. |connect| image:: figures/connect.png
11 | :scale: 30 %
12 |
13 | .. |arrow| image:: figures/arrow.png
14 | :scale: 30 %
15 |
16 | .. image:: figures/runtime1.png
17 |
18 | ----
19 |
20 | .. image:: figures/runtime2.png
21 |
22 | Once the green checkmark appears |checkmark| , the code blocks can be executed or pressing SHIFT+ENTER.
23 |
24 | .. |checkmark| image:: figures/connected.png
25 | :scale: 50 %
26 |
27 | .. image:: figures/wavefunction_anim.gif
28 |
29 | .. note::
30 | The GUI widgets become active only after executing the cell.
31 |
32 | Viewing the Code
33 | ----------------
34 | If you would like to view the source code, click the |show-code| button near the top of any cell to reveal the Python code behind each animation:
35 |
36 | .. |show-code| image:: figures/show_code_button.png
37 | :scale: 25 %
38 |
39 | .. image:: figures/show_code_anim.gif
40 |
41 | Saving Changes
42 | --------------
43 | Keep in mind that any cloud files created or changes made to the source code are lost when the runtime is disconnected. If you would like save your work, save a copy to your Google Drive account with the |drive-copy| button on the top menu (alternatively File -> Save a copy in Drive).
44 |
45 | .. |drive-copy| image:: figures/copy_to_drive.png
46 | :scale: 25 %
47 |
48 | .. image:: figures/copy_drive_location.png
49 |
50 |
--------------------------------------------------------------------------------
/figures/NB1_wavefunction.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/figures/NB1_wavefunction.png
--------------------------------------------------------------------------------
/figures/NB2_anthracene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/figures/NB2_anthracene.png
--------------------------------------------------------------------------------
/figures/NB3_density.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/figures/NB3_density.png
--------------------------------------------------------------------------------
/figures/graphical_abstract.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/figures/graphical_abstract.png
--------------------------------------------------------------------------------
/notebooks/notebook_functions/NB1_functions.py:
--------------------------------------------------------------------------------
1 | # Functions used in Colab Notebook 1 (https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB1_3D_PIB.ipynb)
2 |
3 | def psi_reg(x, y, z, q_nx=1, q_ny=1, q_nz=1, lx=1, ly=1, lz=1):
4 | '''Numeric representation of the normalized 3D PIB wavefunction.
5 |
6 | :param float x: Cartesian spatial x variable.
7 | :param float y: Idem for y.
8 | :param float z: Idem for z.
9 | :param int q_nx: Quantum number specifying the state along x.
10 | :param int q_ny: Idem for y.
11 | :param int q_nz: Idem for z.
12 | :param int lx: Box dimension along x in Bohr.
13 | :param int ly: Idem for y.
14 | :param int lz: Idem for z.
15 |
16 | :return: Wavefunction evaluated at a point or array if input x, y, z are arrays.
17 |
18 | Example:
19 |
20 | >>> # value of the wavefunction at the center of a 3x3x3 box
21 | >>> # in the state 111
22 | >>> psi_reg(1.5, 1.5, 1.5, 1, 1, 1, 3, 3, 3)
23 | 0.5443310539518174
24 | '''
25 | wvfn = (np.sqrt(8 / (lx * ly * lz)) * np.sin((q_nx * np.pi * x) / lx) * np.sin((q_ny * np.pi * y) / ly) * np.sin((q_nz * np.pi * z) / lz) )
26 | return wvfn
27 |
28 | def psi_ener(qnx, qny, qnz, lx, ly, lz):
29 | '''Calculates energy of 3D PIB state.
30 |
31 | :param int qnx: Quantum number specifying the state along x.
32 | :param int qny: Idem for y.
33 | :param int qnz: Idem for z.
34 | :param int lx: Box dimension along x in Bohr.
35 | :param int ly: Idem for y.
36 | :param int lz: Idem for z.
37 |
38 | :return: Float energy value in Ha.
39 |
40 | Example:
41 |
42 | >>> # Energy of 111 state of an 8x8x3 box
43 | >>> psi_ener(1, 1, 1, 8, 8, 3)
44 | 0.702524
45 | '''
46 | e_level = (4*np.pi**2/8)*((qnx/lx)**2 + (qny/ly)**2 + (qnz/lz)**2)
47 | return np.round(e_level, decimals=6)
48 |
49 | def markdown_wvfn(q_nx, q_ny, q_nz, lx, ly, lz):
50 | '''Displays LaTeX equation of 3D wavefunction.
51 |
52 | :param int q_nx: Quantum number specifying the state along x.
53 | :param int q_ny: Idem for y.
54 | :param int q_nz: Idem for z.
55 | :param int lx: Box dimension along x in Bohr.
56 | :param int ly: Idem for y.
57 | :param int lz: Idem for z.
58 |
59 | Example:
60 |
61 | >>> # Wavefunction of 321 state in 8x8x3 box
62 | >>> markdown_wvfn(3, 2, 1, 8, 8, 3)
63 |
64 | .. math::
65 |
66 | \\text{State:}\ \ \psi_{3,2,1}(x,y,z)=\sqrt{\\frac{8}{(8)(8)(3)}}\\sin\\bigg(\\frac{3\pi x}{8}\\bigg)\sin\\bigg(\\frac{2\pi y}{8}\\bigg)\sin\\bigg(\\frac{1\pi z}{3}\\bigg)
67 |
68 | '''
69 | subscript = f'{q_nx},{q_ny},{q_nz}'
70 | sqrt_denom = f'({lx})({ly})({lz})'
71 | display(Markdown('## $$ \\text{State: \ \ } \psi_{' + subscript + '}(x,y,z)=' +
72 | '\sqrt{\\frac{8}{' + sqrt_denom + '}}' +
73 | '\sin{\\Big(\\frac{' + f'{q_nx}' '\pi x}{' + f'{lx}' + '}\\Big)}' +
74 | '\sin{\\Big(\\frac{' + f'{q_ny}' '\pi y}{' + f'{ly}' + '}\\Big)}' +
75 | '\sin{\\Big(\\frac{' + f'{q_nz}' '\pi z}{' + f'{lz}' + '}\\Big)} \\newline $$'))
76 |
77 | def markdown_ener(l_x, l_y, l_z):
78 | '''Displays LaTeX equation of energy.
79 |
80 | :param int l_x: Box dimension along x in Bohr.
81 | :param int l_y: Idem for y.
82 | :param int l_z: Idem for z.
83 |
84 | Example:
85 |
86 | >>> # Energy expression for a 16x8x3 Box
87 | >>> markdown_ener(16, 8, 3)
88 |
89 | .. math::
90 |
91 | E_{n_x,n_y,n_z}= \\frac{\pi^2}{2}\\bigg[\\bigg(\\frac{n_x}{16}\\bigg)^2 + \\bigg(\\frac{n_y}{8}\\bigg)^2 +\\bigg(\\frac{n_z}{3}\\bigg)^2\\bigg]
92 |
93 | '''
94 | display(Markdown('## $$ E_{n_x,n_y,n_z} = \\frac{\pi^2}{2} \\Bigl[ ' +
95 | ' \\Bigl(\\frac{n_x}{' + f'{l_x}' + '}\\Bigl)^2 +' +
96 | ' \\Bigl(\\frac{n_y}{' + f'{l_y}' + '}\\Bigl)^2 +' +
97 | ' \\Bigl(\\frac{n_z}{' + f'{l_z}' + '}\\Bigl)^2\\Bigl] \\newline$$'))
98 |
99 | def isoplotter(nx_val, ny_val, nz_val, lx, ly, lz, psi_square=False, plot_save=True, plotting_lib='plotly'):
100 | '''Displays and saves isosurface of 3D PIB wavefunction.
101 |
102 | :param int nx_val: Quantum number specifying the state along x.
103 | :param int ny_val: Idem for y.
104 | :param int nz_val: Idem for z.
105 | :param int lx: Box dimension along x in Bohr.
106 | :param int ly: Idem for y.
107 | :param int lz: Idem for z.
108 | :param bool psi_square: calculate prob. density (true) or wavefunction (false)
109 | :param bool plot_save: save a .png file of display
110 | :param str plotting_lib: 3d plotting library to use (plotly or ipyvol)
111 |
112 | Example:
113 |
114 | >>> # Render the 321 state of a 8x8x3 PIB system
115 | >>> isoplotter(3, 2, 1, 8, 8, 3, psi_square=False, plot_save=False, plotting_lib='plotly')
116 |
117 | .. image:: figures/321_wavefunction.png
118 |
119 | >>> # Render the 512 probability density of a 16x8x3 PIB system
120 | >>> isoplotter(5, 1, 2, 16, 8, 3, psi_square=True, plot_save=False, plotting_lib='plotly')
121 |
122 | .. image:: figures/512_prob_den.png
123 |
124 | '''
125 | # construct 3d grid of points
126 | nx_p, ny_p, nz_p = 7 * lx, 7 * ly, 7 * lz
127 | xp, yp, zp = np.linspace(0, lx, nx_p), np.linspace(0, ly, ny_p), np.linspace(0, lz, nz_p)
128 | X, Y, Z = np.meshgrid(xp, yp, zp, indexing='ij')
129 | psi = psi_reg(X, Y, Z, nx_val, ny_val, nz_val, lx, ly, lz)
130 | norm_psi = psi_reg(X, Y, Z, nx_val, ny_val, nz_val, lx, ly, lz)**2
131 |
132 | # ipyvolume potting commands
133 | if plotting_lib == 'ipyvol':
134 | ipv.clear()
135 | fig = ipv.figure(title='PIB',width=500, height=500)
136 | fig.camera.type = 'OrthographicCamera'
137 | if psi_square:
138 | norm_sur = ipv.pylab.plot_isosurface(norm_psi, color='red', level=norm_psi.mean(), controls=True,
139 | description='prob. density')
140 | else:
141 | pos_values = np.ma.array(psi, mask = psi < 0.0)
142 | if nx_val == ny_val == nz_val == 1:
143 | pos_sur = ipv.pylab.plot_isosurface(psi,color='red', level=np.sqrt(norm_psi.mean()), controls=True,
144 | description='positive')
145 | else:
146 | pos_sur = ipv.pylab.plot_isosurface(psi, color='red', level=np.sqrt(norm_psi.mean()), controls=True,
147 | description='positive')
148 | neg_sur = ipv.pylab.plot_isosurface(psi, color='blue', level=-np.sqrt(norm_psi.mean()), controls=True,
149 | description='negative')
150 | ipv.style.box_off()
151 | ipv.squarelim()
152 | ipv.view(0,-75)
153 | ipv.xyzlabel('lx', 'ly', 'lz')
154 | ipv.show()
155 |
156 | # plotly potting commands
157 | elif plotting_lib == 'plotly':
158 | global figure
159 | if psi_square:
160 | den_isoval = norm_psi.mean()
161 | figure = go.Figure(data=go.Isosurface(
162 | x=X.flatten(),
163 | y=Y.flatten(),
164 | z=Z.flatten(),
165 | value=norm_psi.flatten(),
166 | colorscale='BlueRed',
167 | isomin=-den_isoval,
168 | isomax=den_isoval,
169 | surface_count=2,
170 | showscale=False,
171 | caps=dict(x_show=False, y_show=False, z_show=False)
172 | ))
173 | figure.update_layout(scene = dict(
174 | xaxis_title='x',
175 | yaxis_title='y',
176 | zaxis_title='z',
177 | aspectmode='data'),
178 | width=800,
179 | height=500,
180 | title_text=f'Prob. Den., isovalue = {den_isoval:.4f}')
181 | figure.show()
182 | else:
183 | wfn_isoval = np.sqrt(norm_psi.mean())
184 | figure = go.Figure(data=go.Isosurface(
185 | x=X.flatten(),
186 | y=Y.flatten(),
187 | z=Z.flatten(),
188 | value=psi.flatten(),
189 | colorscale='BlueRed',
190 | isomin=-wfn_isoval,
191 | isomax=wfn_isoval,
192 | surface_count=2,
193 | showscale=False,
194 | caps=dict(x_show=False, y_show=False, z_show=False)
195 | ))
196 | figure.update_layout(scene = dict(
197 | xaxis_title='x',
198 | yaxis_title='y',
199 | zaxis_title='z',
200 | aspectmode='data'),
201 | width=800,
202 | height=500,
203 | title_text=f'Wavefunction, isovalue = {wfn_isoval:.4f}')
204 | figure.show()
205 |
206 | def plot_saver(b):
207 | if lib_dropdown.value == 'ipyvol':
208 | ipv.savefig(f'{filename_text_ipv.value}.png', width=1200, height=1200)
209 | elif lib_dropdown.value == 'plotly':
210 | #plotly.offline.plot(figure, filename='test.html')
211 | figure.write_image(f'{filename_text_ipv.value}.png', width=800, height=800)
212 |
213 |
214 |
--------------------------------------------------------------------------------
/notebooks/notebook_functions/NB2_functions.py:
--------------------------------------------------------------------------------
1 | # Functions used in Colab Notebook 2 (https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB2_PAH_HF.ipynb)
2 |
3 |
4 | def FF_optimization(molecule_num:int):
5 | '''Performs a MMFF94 optimization of a PAH and displays 3D rendering of result.
6 |
7 | :param int molecule_num: Number corresponding to a PAH from the menu.
8 |
9 | PAH Options:
10 |
11 | .. list-table::
12 | :widths: 10 15 5 20 15 15
13 | :header-rows: 1
14 |
15 | * - **Table Index**
16 | - **PAH**
17 | - **Rings**
18 | - **Dimensions (Bohr)**
19 | - **pi electrons**
20 | - **Fuse-type**
21 | * - 1
22 | - benzene
23 | - 1
24 | - 8 x 8 x 3
25 | - 6
26 | - linear
27 | * - 2
28 | - naphthalene
29 | - 2
30 | - 12 x 8 x 3
31 | - 10
32 | - linear
33 | * - 3
34 | - anthracene
35 | - 3
36 | - 16 x 8 x 3
37 | - 14
38 | - linear
39 | * - 4
40 | - tetracene
41 | - 4
42 | - 20 x 8 x 3
43 | - 18
44 | - linear
45 | * - 5
46 | - pentacene
47 | - 5
48 | - 24 x 8 x 3
49 | - 22
50 | - linear
51 | * - 6
52 | - hexacene
53 | - 6
54 | - 28 x 8 x 3
55 | - 26
56 | - linear
57 | * - 7
58 | - heptacene
59 | - 7
60 | - 32 x 8 x 3
61 | - 30
62 | - linear
63 | * - ---
64 | - ---
65 | - ---
66 | - ---
67 | - ---
68 | - ---
69 | * - 8
70 | - pyrene
71 | - 4
72 | - 16 x 12 x 3
73 | - 16
74 | - non-linear
75 | * - 9
76 | - perylene
77 | - 5
78 | - 20 x 12 x 3
79 | - 20
80 | - non-linear
81 | * - 10
82 | - coronene
83 | - 7
84 | - 20 x 16 x 3
85 | - 24
86 | - non-linear
87 |
88 | Example:
89 |
90 | >>> # Perform a geometry optimization of heptacene
91 | >>> FF_optimization(7)
92 |
93 | .. image:: figures/heptacene.png
94 | :scale: 70 %
95 | :align: center
96 |
97 | '''
98 | global molecule_smiles, molecule_smiles_copy # global for the subsequent code block
99 | molecule_smiles = [] # list of tuples of rdkit object (from Smiles) and molecule name
100 | cyclobut = Chem.MolFromSmiles("c1ccc1"), 'cylobut'
101 | ring1 = Chem.MolFromSmiles("c1ccccc1"), 'Benzene'
102 | ring2 = Chem.MolFromSmiles("c1c2ccccc2ccc1"), 'Naphthalene'
103 | ring3 = Chem.MolFromSmiles("c1ccc2cc3ccccc3cc2c1"), 'Anthracene'
104 | ring4 = Chem.MolFromSmiles("c1c2cc3cc4ccccc4cc3cc2ccc1"), 'Tetracene'
105 | ring5 = Chem.MolFromSmiles("c1ccc2cc3cc4cc5ccccc5cc4cc3cc2c1"), 'Pentacene'
106 | ring6 = Chem.MolFromSmiles("c1c2cc3cc4cc5cc6ccccc6cc5cc4cc3cc2ccc1"), 'Hexacene'
107 | ring7 = Chem.MolFromSmiles("C1=CC=C2C=C3C=C4C=C5C=C6C=C7C=CC=CC7=CC6=CC5=CC4=CC3=CC2=C1"), 'Heptacene'
108 | ring4a = Chem.MolFromSmiles("c1cc2cccc3c2c4c1cccc4cc3"), 'Pyrene'
109 | ring5a = Chem.MolFromSmiles("c1ccc5cccc4c5c1c2cccc3cccc4c23"), 'Perylene'
110 | ring7a = Chem.MolFromSmiles("c1cc2ccc3ccc4ccc5ccc6ccc1c7c2c3c4c5c67"), 'Coronene'
111 | molecule_smiles.append(cyclobut)
112 | molecule_smiles.append(ring1)
113 | molecule_smiles.append(ring2)
114 | molecule_smiles.append(ring3)
115 | molecule_smiles.append(ring4)
116 | molecule_smiles.append(ring5)
117 | molecule_smiles.append(ring6)
118 | molecule_smiles.append(ring7)
119 | molecule_smiles.append(ring4a)
120 | molecule_smiles.append(ring5a)
121 | molecule_smiles.append(ring7a)
122 | molecule_smiles_copy = molecule_smiles.copy()
123 |
124 | molecule = molecule_smiles[molecule_num][0] # user-selected molecule from the list
125 | global mol, xyz_geom
126 | mol = Chem.Mol(molecule)
127 | mol = AllChem.AddHs(mol, addCoords=True)
128 | AllChem.EmbedMolecule(mol)
129 | AllChem.MMFFOptimizeMolecule(mol) # uses MMFF94 Force Field to optimize structure
130 |
131 | # format structure into readable xyz coordinates
132 | xyz_geom = []
133 | for lines in Chem.MolToXYZBlock(mol).split("\n")[2:]:
134 | strip = lines.strip()
135 | if strip:
136 | xyz_geom.append(strip)
137 | xyz_geom = "\n".join(xyz_geom)
138 |
139 | # render 3D ball-and-stick model of structure
140 | display(Markdown('
'))
141 | display(Markdown(' MMFF94 Optimized Geometry'))
142 | view = py3Dmol.view(
143 | data=Chem.MolToMolBlock(mol),
144 | style={"stick": {}, "sphere": {"scale": 0.3}},
145 | width=400,
146 | height=300,
147 | )
148 | view.zoomTo()
149 | view.show()
150 |
151 | def run_calculation(xyz_struc, basis="STO-3G"):
152 | '''Performs a HF/STO-3G single-point calculation on a PAH structure. Energy results are displayed in a Markdown table.
153 |
154 | :param str xyz_struc: geometry of PAH in xyz format
155 | :param str basis: basis set; see options: https://pyscf.org/_modules/pyscf/gto/basis.html
156 |
157 | Heptacene Example:
158 |
159 | .. image:: figures/heptacene_scf.png
160 |
161 | '''
162 | clear_output(wait=True)
163 | display(widgets.VBox([top_panel, output]))
164 | global mf, mol_quantum, molecule_index, molecule_name
165 | # create pyscf object from input structure
166 | mol_quantum = gto.M(atom=xyz_struc, basis=basis, unit="ANG", symmetry=False)
167 | mol_quantum.build();
168 | molecule_index = PAH_d_options[PAH_dropdown.value-1][1]
169 | molecule_name = PAH_d_options[PAH_dropdown.value-1][0].split('-')[1]
170 | print(f"Peforming HF/STO-3G single-point caculation on {molecule_name}...")
171 | mf = scf.RHF(mol_quantum).run(verbose=0)
172 | print("SCF Converged!")
173 | mos = mf.mo_coeff
174 | elec_ener = mf.energy_elec()[0] # electronic energy
175 | nuc_ener = mf.energy_nuc() # nuclear "
176 | total_ener = mf.energy_tot() # total "
177 | # Markdown table display:
178 | display(Markdown('
'))
179 | display(Markdown(f'''Component|Energy (Ha)
180 | :---:|:---:
181 | electronic | {elec_ener:.6f}
182 | nuclear | {nuc_ener:.6f}
183 | **total** | {total_ener:.6f}
184 | '''))
185 | display(Markdown('
'))
186 |
187 | def get_HL_gap(mf):
188 | '''Finds HOMO-LUMO gap of molecule from HF/STO-3G calculation result.
189 |
190 | :param pyscf.scf.RHF mf: pyscf HF calculation object
191 | :return: Float representing HL gap in electron-volts.
192 |
193 | '''
194 | global homo_num, lumo_num
195 | homo_num = np.count_nonzero(mf.mo_occ == 2) - 1 # HOMO index
196 | lumo_num = np.count_nonzero(mf.mo_occ == 2) # LUMO index
197 | homo_en = mf.mo_energy[homo_num] # HOMO energy
198 | mos_occ = mf.mo_coeff[:,:lumo_num]
199 | lumo_en = mf.mo_energy[lumo_num] # LUMO energy
200 | mos_unocc = mf.mo_coeff[:,lumo_num:]
201 | hl_gap = abs(lumo_en - homo_en)
202 | hl_gap_ev = hl_gap * 27.2114 # 1 Ha = 27.2114 eV conversion
203 | return hl_gap_ev
204 |
205 | def elec_properties(b):
206 | '''Prints dipole moment, HL gap, and Mulliken charges from HF calculation result.
207 |
208 | :param b: Button click
209 |
210 | '''
211 | HL_gap = get_HL_gap(mf)
212 | capture_output = mf.dip_moment(verbose=3)
213 | print(f"HOMO-LUMO Gap (eV): {HL_gap:.4f}")
214 | print("===============================")
215 | capture = mf.mulliken_pop(verbose=3)
216 | for atom in mol.GetAtoms():
217 | atom.SetProp('molAtomMapNumber', str(atom.GetIdx()+1))
218 | display(mol)
219 | properties_button.disabled = True
220 |
221 | def click_scf(b):
222 | run_calculation(xyz_geom)
223 | display(Markdown('---'))
224 | display(properties_button)
225 | display(Markdown('
'))
226 | scf_button.disabled = True
227 | properties_button.disabled = False
228 |
229 | def mo_diagram(mf, show_diagram, show_table, savefig, filename):
230 | '''Displays MO energy level diagram and dataframe.
231 |
232 | :param pyscf.scf.RHF mf: pyscf HF calculation object.
233 | :param bool show_diagram: Display matplotlib energy diagram.
234 | :param bool show_table: Display same data as Pandas dataframe.
235 | :param bool savefig: Save figure as .png file.
236 | :param str filename: filename to save .png file to (w/o file extension).
237 |
238 | Example:
239 |
240 | .. image:: figures/heptacene_diagram.png
241 | :scale: 70 %
242 |
243 | '''
244 | # Find HOMO-LUMO gap
245 | homo_num = np.count_nonzero(mf.mo_occ == 2) - 1
246 | lumo_num = np.count_nonzero(mf.mo_occ == 2)
247 | homo_en = mf.mo_energy[homo_num]
248 | mos_occ = mf.mo_coeff[:,:lumo_num]
249 | lumo_en = mf.mo_energy[lumo_num]
250 | mos_unocc = mf.mo_coeff[:,lumo_num:]
251 | hl_gap = abs(lumo_en - homo_en)
252 | hl_gap_ev = hl_gap * 27.2114
253 |
254 | # Create a list MO labels (i.e. HOMO, HOMO-1, etc.)
255 | MO_labels = []
256 | for i in range(len(mf.mo_occ)-lumo_num):
257 | if i == 0:
258 | MO_labels.append('LUMO')
259 | else:
260 | MO_labels.append(f'LUMO+{i}')
261 | MO_labels.reverse()
262 | for i in range(homo_num+1):
263 | if i == 0 :
264 | MO_labels.append('HOMO')
265 | else:
266 | MO_labels.append(f'HOMO-{i}')
267 | MO_labels.reverse()
268 |
269 | table = pd.DataFrame({"En (Ha)": mf.mo_energy, "Occ.": mf.mo_occ.astype('int'), "Type": MO_labels})
270 | table.index.name = 'MO #'
271 |
272 | global frontier_nums, frontier_labels
273 | frontier_nums = np.arange(homo_num-4,lumo_num+5,1)
274 | frontier_labels = table.iloc[frontier_nums]['Type'].tolist()
275 | energies = table.iloc[homo_num-4:lumo_num+5][::-1]['En (Ha)']
276 |
277 | MO_labels2 = []
278 | # Create a list MO labels (i.e. HOMO, HOMO-1, etc.)
279 | for i in range(len(mf.mo_occ)-lumo_num):
280 | MO_labels2.append(f'LUMO+{i}')
281 | MO_labels2.reverse()
282 | for i in range(homo_num+1):
283 | MO_labels2.append(f'HOMO-{i}')
284 | MO_labels2.reverse()
285 |
286 | # pandas dataframe for the matplotlib diagram
287 | table2 = pd.DataFrame({"En (Ha)": mf.mo_energy, "Occ.": mf.mo_occ.astype('int'), "Type": MO_labels2})
288 | datat = table2.values.tolist()
289 |
290 | y = [] # energy values
291 | orb = 5
292 | for i in datat:
293 | if int(i[2][5:]) < orb:
294 | y.append(round(i[0],4))
295 | x = [1]*len(y)
296 | for i in range(0,len(y)):
297 | if y[i] in y[:i]:
298 | count = list(y[:i]).count(y[i])
299 | x[i] += count*0.3 # x value offset for degenerate orbitals (avoids overlap)
300 |
301 | # Building up the diagram
302 | fig = plt.figure(figsize=(7, 6))
303 | ax = fig.add_subplot(1, 1, 1)
304 | plt.ylabel("Energy (Ha)", labelpad=7)
305 | plt.scatter(x[orb:], y[orb:], marker=0, s=1200, linewidths=4, color='#F97306', label='virtual')
306 | plt.scatter(x[:orb], y[:orb], marker=0, s=1200, linewidths=4, color='green', label='occupied')
307 | annotations = []
308 | annotations2 = []
309 | for i in range(len(x)):
310 | if i < orb:
311 | if i != 0:
312 | annotations.append('HOMO-' + str(i))
313 | annotations2.append('LUMO+' + str(i))
314 | else:
315 | annotations.append('HOMO')
316 | annotations2.append('LUMO')
317 | all_annotations = annotations[::-1]+annotations2
318 | for i, label in enumerate(all_annotations):
319 | if list(y).count(y[i])==1:
320 | plt.annotate(label, (x[i] + 0.005, y[i]-0.01), size=8)
321 | plt.text(x[i]-0.42, y[i]-0.01, "{:.3f}".format(y[i]), size=8)
322 | else:
323 | plt.annotate(label, (x[i] - 0.20, y[i] + 0.015), size=8)
324 | if y[i] not in y[:i]:
325 | plt.text(x[i]-0.42, y[i]-0.01, "{:.3f}".format(y[i]), size=8)
326 | plt.rcParams["legend.markerscale"] = 0.50
327 | plt.legend(loc='upper right', handletextpad=-0.3)
328 | plt.title(f'{molecule_name} Frontier Orbitals')
329 | plt.xticks([])
330 | plt.xlim([0.3, 3])
331 | plt.ylim([min(y) - 0.1, max(y) + 0.1])
332 |
333 | # display HOMO-LUMO gap value
334 | plt.text(0.80, 0.730, f'H-L Gap: {hl_gap:.3f} Ha\n ({hl_gap_ev:.3f} ev)',
335 | size=15, horizontalalignment='center', verticalalignment='center',
336 | transform=ax.transAxes)
337 |
338 | # adding the stick molecule
339 | mol_copy = molecule_smiles_copy[molecule_index][0]
340 | mol_copy_pil = MolToImage(mol_copy)
341 | imagebox = OffsetImage(mol_copy_pil, zoom=0.5, interpolation='bicubic')
342 | imagebox.image.axes = ax
343 | ab = AnnotationBbox(imagebox, (0.5,0.53),
344 | xybox=(120., -80.),
345 | xycoords='figure fraction',
346 | boxcoords="offset points",
347 | frameon=False)
348 | ax.add_artist(ab)
349 | plt.tight_layout()
350 |
351 | # save/display control events
352 | if savefig == True:
353 | plt.savefig(f'{filename}.png', dpi=800)
354 | if show_diagram == True:
355 | plt.show()
356 | elif show_diagram == False:
357 | plt.close()
358 | display(Markdown(' '))
359 | if show_table == True:
360 | display(table.iloc[homo_num-4:lumo_num+5][::-1])
361 |
362 | def on_click_save(b):
363 | mo_diagram(mf_mo, show_diagram=False, show_table=False, savefig=True, filename=filename_text_mpl.value)
364 |
365 | def get_orbital(i, label):
366 | '''Saves a .cube file of orbital and prob. density from the above calculation.
367 |
368 | :param int i: index to iterate over in for loop
369 | :param str label: MO label, i.e. HOMO, HOMO-1, etc
370 |
371 | '''
372 | tools.cubegen.orbital(mol_quantum, f'{label}.cube', mf.mo_coeff[:,i],nx=80,ny=80,nz=80)
373 | square_cube = cube_tools.cube(f'{label}.cube')
374 | pos = np.ma.array(square_cube.data, mask = square_cube.data < 0)
375 | good_isovalues.append([f'{label}',pos.mean()])
376 | square_cube.square_cube(power=2)
377 | pos = np.ma.array(square_cube.data, mask = square_cube.data < 0)
378 | good_isovalues.append([f'{label}_square',pos.mean()])
379 | square_cube.write_cube(f'{label}_square.cube')
380 |
381 | def draw_orbital(label, psi_square=False, show_noninteractive_png=False):
382 | '''Renders isosurface of selected orbital from HF/STO-3G calculation.
383 |
384 | :param str label: orbital designation, e.g. HOMO, HOMO-1, etc
385 | :param bool psi_square: show orbital (False) or probability density (True)
386 | :param bool show_noninteractive_png: render a static image (True) or interactive 3D display (False)
387 |
388 | Example:
389 |
390 | >>> # Display probability density of HOMO-3 orbital of heptacene (calculated in a previous cell)
391 | >>> draw_orbital('HOMO-3', psi_square=True, show_noninteractive_png=False)
392 |
393 | .. image:: figures/heptacene_HOMO3.png
394 | :scale: 55 %
395 | :align: center
396 |
397 | '''
398 | view = py3Dmol.view(width=500,height=500)
399 | if psi_square:
400 | index = int(np.where(good_isovalues == f'{label}_square')[0])
401 | isoval = float(good_isovalues[index][1])
402 | print('-----------------------------------------')
403 | print(f'isovalue: {isoval:.4f}')
404 | print('rendering...')
405 | view.addVolumetricData(grid_data[f'{label}_square'], "cube", {'isoval': isoval, 'color': "red", 'opacity': 0.90})
406 | else:
407 | with open(f'{label}.cube') as f:
408 | cube_data = f.read()
409 | index = int(np.where(good_isovalues == f'{label}')[0])
410 | isoval = float(good_isovalues[index][1])*2
411 | print('----------------------------------------')
412 | print(f'isovalue: {isoval:.4f}')
413 | print('rendering...')
414 | view.addVolumetricData(grid_data[f'{label}'], "cube", {'isoval': isoval, 'color': "red", 'opacity': 0.90})
415 | view.addVolumetricData(grid_data[f'{label}'], "cube", {'isoval': -isoval, 'color': "blue", 'opacity': 0.90})
416 | view.addModel(Chem.MolToMolBlock(mol), 'mol')
417 | view.setStyle({'stick':{}, 'sphere': {"scale":0.3}})
418 | if show_noninteractive_png:
419 | view.zoomTo()
420 | view.show()
421 | view.png()
422 | else:
423 | view.zoomTo()
424 | view.show()
425 |
--------------------------------------------------------------------------------
/notebooks/notebook_functions/NB3_functions.py:
--------------------------------------------------------------------------------
1 | # Functions used in Colab Notebook 3 (https://colab.research.google.com/github/tjz21/DFT_PIB_Code/blob/main/notebooks/NB3_DFT_PIB.ipynb)
2 |
3 | def edge_cleaner(func_3d, nx, ny, nz, num_edges=1):
4 | '''Sets outermost layer (or two) of grid values of a 3D function to zero.
5 |
6 | This is needed for the density because of the discontinuities in the numeric 2nd derivatives at the box edges.
7 |
8 | :param np.array func_3d: Flattened 3D grid of points.
9 | :param int nx: Number of grid points in x-direction.
10 | :param int ny: Idem for y.
11 | :param int nz: Idem for z.
12 | :param int num_edges: Number of border layers to set to zero (either 1 or 2).
13 | :return: A flattened array of grid points.
14 | '''
15 | func_3d = func_3d.reshape(nx, ny, nz)
16 | if num_edges == 1:
17 | func_3d[0,:,:] = 0
18 | func_3d[-1,:,:] = 0
19 | func_3d[:,0,:] = 0
20 | func_3d[:,-1,:] = 0
21 | func_3d[:,:,0] = 0
22 | func_3d[:,:,-1] = 0
23 | elif num_edges == 2:
24 | func_3d[0,:,:] = 0
25 | func_3d[-1,:,:] = 0
26 | func_3d[:,0,:] = 0
27 | func_3d[:,-1,:] = 0
28 | func_3d[:,:,0] = 0
29 | func_3d[:,:,-1] = 0
30 | func_3d[1,:,:] = 0
31 | func_3d[-2,:,:] = 0
32 | func_3d[:,1,:] = 0
33 | func_3d[:,-2,:] = 0
34 | func_3d[:,:,1] = 0
35 | func_3d[:,:,-2] = 0
36 | return func_3d.flatten()
37 |
38 | def integ_3d(func_3d, dx, dy, dz):
39 | '''Integrates a 3D function over all defined space.
40 |
41 | :param np.array func_3d: 3D grid of points
42 | :param float dx: Differential volume element in x-direction.
43 | :param float dy: Idem for y.
44 | :param float dz: Idem for z.
45 | :return: Integrated 3D function as scalar value.
46 |
47 | '''
48 | return np.sum(func_3d * dx * dy * dz)
49 |
50 | def norm_psi_and_den(e_vecs, occ_states, dx, dy, dz):
51 | '''Normalizes raw eigenvectors from the solver and finds electron density.
52 |
53 | :param np.array e_vecs: Array of eigenvectors from the eigenvalue solver.
54 | :param int occ_states: Number of occupied KS states (i.e. # electrons / 2).
55 | :param float dx: Differential volume element in x-direction.
56 | :param float dy: Idem for y.
57 | :param float dz: Idem for z.
58 |
59 | :return:
60 | - **norm_psi** (*np.array*): Array of normalized eigenvectors.
61 | - **el_den** (*np.array*): Electron density as array.
62 | '''
63 | norm_psi = np.zeros_like(e_vecs)
64 | el_den = np.zeros_like(e_vecs[:,0])
65 | for i in range(e_vecs.shape[1]):
66 | norm_psi[:,i] = e_vecs[:,i]/np.sqrt(integ_3d(e_vecs[:,i]**2, dx, dy, dz))
67 | for i in range(occ_states):
68 | el_den += 2* norm_psi[:,i]**2
69 | return norm_psi, el_den
70 |
71 | def noninter_kin_e(norm_eigenvecs, occ_states, kin_mat, dx, dy, dz, nx, ny, nz):
72 | '''Finds noninteracting KS kinetic energy.
73 |
74 | :param np.array norm_eigenvecs: array of normalized eigenvectors as columns/rows
75 | :param int occ_states: number of occupied KS states (i.e. # electrons / 2)
76 | :param scipy.sparse.sparray kin_mat: kinetic energy operator as sparse matrix
77 | :param float dx: Differential volume element in x-direction.
78 | :param float dy: Idem for y.
79 | :param float dz: Idem for z.
80 | :param int nx: Number of grid points in each x-direction.
81 | :param int ny: Idem for y.
82 | :param int nz: Idem for z.
83 | :return: Scalar energy value in Ha.
84 | '''
85 | kin_energy_values = []
86 | for eig in norm_eigenvecs.T[:occ_states]:
87 | inner_prod = eig*kin_mat.dot(eig)
88 | inner_prod = edge_cleaner(inner_prod, nx, ny, nz, num_edges=1)
89 | orbital_k_en = integ_3d(inner_prod, dx, dy, dz)
90 | kin_energy_values.append(orbital_k_en)
91 | return sum(kin_energy_values)
92 |
93 | def grid_density(l_x, l_y, l_z, plotting_lib='plotly'):
94 | '''Generates 3D scatter plot representing grid for DFT calculations.
95 |
96 | :param int l_x: Box length in x-direction.
97 | :param int l_y: Idem for y.
98 | :param int l_z: Idem for z.
99 |
100 | Example:
101 |
102 | >>> # View numerical grid for a 16x8x3 Box
103 | >>> grid_density(16, 8, 3, plotting_lib='plotly')
104 |
105 | .. image:: figures/grid_density.png
106 | :align: center
107 | :scale: 60 %
108 |
109 | '''
110 | nx, ny, nz = (5 * l_x), (5 * l_y), (5 * l_z)
111 | gpoints = (nx * ny * nz)
112 | xp, yp, zp = np.linspace(0, l_x, nx), np.linspace(0, l_y, ny), np.linspace(0, l_z, nz)
113 | X, Y, Z = np.meshgrid(xp, yp, zp, indexing='ij')
114 | label = '### Grid Points (5 pts/bohr): '
115 | label += '$x_p \\times y_p \\times z_p ='
116 | label += f'{nx}' + '\\times' + f'{ny}' + '\\times' + f'{nz}' + ' = '+ f'{gpoints}$ '
117 | display(Markdown(label))
118 | if plotting_lib == 'ipyvol':
119 | fig1 = ipv.figure(title='PIB',width=450, height=450)
120 | fig1.camera.type = 'OrthographicCamera'
121 | ipv.quickscatter(X.flatten(), Y.flatten(), Z.flatten(), size=0.5, marker='box',description='grid points')
122 | ipv.squarelim()
123 | ipv.style.box_off()
124 | ipv.show()
125 | elif plotting_lib == 'plotly':
126 | scatter = go.Figure(data=[go.Scatter3d(x=X.flatten(), y=Y.flatten(), z=Z.flatten(),
127 | mode='markers',
128 | marker=dict(size=2,color='red',symbol='square',opacity=0.5))])
129 | scatter.update_layout(scene = dict(xaxis_title='x', yaxis_title='y', zaxis_title='z',
130 | aspectmode='data'), width=900, height=500)
131 | scatter.show()
132 |
133 | def exch_equation(den_number):
134 | '''Displays LaTeX equation of LDA exchange potential.
135 |
136 | :param float den_number: Value of electron density, n(r), at a specific position..
137 |
138 | Example:
139 |
140 | >>> # equation for electronic exchange at a point with a density of 0.50 e/Bohr**3
141 | >>> exch_equation(0.50)
142 |
143 | .. math::
144 |
145 | v_{\\text{X, n(r)=0.50}}^{\\text{LDA}}= - \\frac{3}{4} \left(\\frac{3}{\pi}\\right)^{1/3} (0.50)^{1/3} = -0.586\ \\text{Ha/e}
146 |
147 | '''
148 | exch_pot_number = np.round(-(3/4)*(3/np.pi)**(1/3)*den_number**(1/3), decimals=3)
149 | text = Markdown('## $v_{\\text{X},\ n(r)= \ ' + f'{den_number:.2f}' + '}^{\\text{LDA}} = -\\frac{3}{4}' +
150 | '\left(\\frac{3}{\pi}\\right)^{1/3}' +
151 | f'({den_number:.2f})' + '^{1/3}' +
152 | f'={exch_pot_number:.3f}\ ' + '\\text{Ha/e}$')
153 | display(text)
154 | clear_output(wait=True)
155 |
156 | def exch_pot_eq(den_number):
157 | '''Displays line plot of LDA exchange with scatterpoint at density value.
158 |
159 | :param float den_number: Value of electron density, n(r).
160 |
161 | Example:
162 |
163 | >>> # plot for lda electronic exchange with a point at a density of 0.50 e/Bohr**3
164 | >>> exch_pot_eq(0.50)
165 |
166 | .. image:: figures/LDA_exch.png
167 |
168 | '''
169 | exch_pot_number = np.round(-(3/4)*(3/np.pi)**(1/3)*den_number**(1/3), decimals=3)
170 | display(Markdown('
'))
171 | x = np.linspace(0, 10, 300)
172 | y = np.round(-(3/4)*(3/np.pi)**(1/3)*x**(1/3), decimals=3)
173 | fig = plt.figure()
174 | ax = fig.add_subplot(1, 1, 1)
175 | plt.cla()
176 | plt.clf()
177 | clear_output(wait=True)
178 | plt.plot(x, y, label=r'Slater exchange')
179 | plt.hlines(exch_pot_number, 0, den_number, colors='black')
180 | plt.vlines(den_number, -2, exch_pot_number, colors='black')
181 | plt.scatter(den_number, exch_pot_number, marker="s", color='red', label='grid point')
182 | plt.title('LDA Exchange Potential', size=15)
183 | plt.xlabel('Density, $n(r)$', size=15)
184 | plt.ylabel('Potential, $v_{\mathrm{X}}^{\mathrm{LDA}}(n(r))$', size=15)
185 | plt.xlim(0,2)
186 | plt.ylim(-1.25,0)
187 | plt.rcParams["legend.markerscale"] = 1.5
188 | plt.legend(loc='upper right', fontsize=15)
189 | plt.show()
190 |
191 | def LDA_c_display(ws_radius):
192 | '''Displays line plot of Chachiyo LDA correlation potential with a scatterpoint and LaTeX equation at the Wigner-Seitz radius (r_s) value.
193 |
194 | :param float ws_radius: Wigner-Seitz radius (inversely related to density).
195 |
196 | Example:
197 |
198 | >>> # display equation and diagram for LDA correlation with point @ r_s = 2.00
199 | >>> LDA_c_display(2.00)
200 |
201 | .. math::
202 |
203 | v_{\\text{C}, r_s=2.00}^{\\text{LDA}}=a\ln\left(1+\\frac{b}{(2.00)}+\\frac{b}{(2.00)^2}\\right) = -0.059\ \\text{Ha/e}
204 |
205 | .. image:: figures/LDA_cor.png
206 |
207 | '''
208 | # a, b, c fitting constants used in paper: 10.1063/1.4958669
209 | a, b, c = (np.log(2)-1)/(2*np.pi**2), 20.4562557, (4*np.pi/3)**(1/3)
210 | lda_expression = lambda rad: a*np.log(1 + b*c*1/(rad) + b*(c**2)*1/(rad))
211 | cor_pot_number = lda_expression(ws_radius)
212 |
213 | # LaTeX Equation
214 | text = Markdown('## $v_{\\text{C},\ r_s=' + f'{ws_radius:.2f}' + '}^{\\text{LDA}}' + '= a\cdot \\text{ln}\left(1+\\frac{b}{' +
215 | f'({ws_radius:.2f})' + '} + \\frac{b}{' + f'({ws_radius:.2f})^2' +
216 | '}\\right)=' + f'{cor_pot_number:.3f}\ ' + '\\text{Ha/e}$')
217 | display(text)
218 |
219 | # Matplotlib Diagram
220 | x = np.linspace(0.001, 10, 100)
221 | y = lda_expression(x)
222 | plt.plot(x, y, label='Chachiyo Correlation')
223 | plt.scatter(ws_radius, cor_pot_number, color='red', marker="s", label='grid point')
224 | plt.hlines(cor_pot_number, 0, ws_radius, colors='black')
225 | plt.vlines(ws_radius, -2, cor_pot_number, colors='black')
226 | plt.title('LDA Correlation Potential', size=15)
227 | plt.xlabel('Wigner-Seitz Radius, $r_s$', size=15)
228 | plt.ylabel('Potential, $v_{\mathrm{C}}^{\mathrm{LDA}}(r_s)$', size=15)
229 | plt.xlim(0,10)
230 | plt.ylim(-0.15,0)
231 | plt.rcParams["legend.markerscale"] = 1.5
232 | plt.legend(loc='upper right', fontsize=15)
233 | plt.show()
234 |
235 | def GGAx_pot_eq(den_num, grad_den_num):
236 | '''Generates line plot of PBE exchange enhancement factor with a LaTeX equation and scatterpoint at the specified density + gradient value.
237 |
238 | :param float den_num: value of the electron density
239 | :param float grad_den_num: gradient of electron density at same point
240 |
241 | Example:
242 |
243 | >>> # Display GGA exchange equation and diagram for n(r) = 0.15 and grad n(r) = 1.00
244 | >>> GGAx_pot_eq(0.15, 1.00)
245 |
246 | .. math::
247 |
248 | s=\\frac{|1.00|}{2 \cdot 3^{1/3} \pi^{2/3} (0.150)^{4/3}}=2.028 \ \ \ F_{x,s=2.028} = 1 + \kappa - \\frac{\kappa}{\left(1 + \\frac{\mu (2.028)^2}{\kappa} \\right)} = 1.425
249 |
250 | .. image:: figures/GGA_exch_plot.png
251 |
252 | '''
253 | kappa, mu = 0.804, 0.21951 # constants used in the paper, 10.1103/PhysRevLett.77.3865
254 | # reduced density gradient, s:
255 | calculate_s = lambda den, grad_den: abs(grad_den)/(2*3**(1/3)*np.pi**(2/3)*den**(4/3))
256 | # enhancement factor, F_s:
257 | calculate_F_s = lambda s: 1 + kappa - kappa/(1 + mu*s**2 / kappa)
258 | s_num = calculate_s(den_num, grad_den_num)
259 | F_s_num = calculate_F_s(s_num)
260 |
261 | # LaTeX equation
262 | equation = '## $s = \\frac{' + f'|{grad_den_num:.3f}|' + '}{2\cdot3' + '^{1/3}\pi^{2/3}' + f'({den_num:.3f})' + '^{4/3}}=' + f'{s_num:.3f}\ '
263 | equation += '\ \ \ \ F_{x,\ s=' + f'{s_num:.3f}' + '} = 1 + \kappa - \\frac{\kappa}{(1+\\frac{\mu' + f'({s_num:.3f})^2' + '}{\kappa})}=' + f'{F_s_num:.3f}\ ' + '$'
264 | equation = Markdown(equation)
265 | display(equation)
266 | display(Markdown('
'))
267 |
268 | # Matplotlib Diagram
269 | x_s = np.linspace(0, 10, 300)
270 | y = calculate_F_s(x_s)
271 | plt.plot(x_s, y, label=r'PBE Exch. Factor')
272 | plt.hlines(F_s_num, 0, s_num, colors='black')
273 | plt.vlines(s_num, -2, F_s_num, colors='black')
274 | plt.scatter(s_num, F_s_num, color='red', marker="s", label='grid point')
275 | plt.title('GGA Enchancement Factor', size=15)
276 | plt.xlabel('RDG, $s$', size=15)
277 | plt.ylabel('$F_x(s)$', size=15)
278 | plt.xlim(0,10)
279 | plt.ylim(0.95, 2.1)
280 | plt.legend(loc='upper left', fontsize=15)
281 | plt.show()
282 |
283 | def hartree_plotter(freq, solve_poisson):
284 | '''Display diagram solving for potential from model density in 1D.
285 |
286 | :param int freq: coefficient for frequency in trig function
287 | :param bool solve_poisson: display (True) potential from trig functionc
288 |
289 | Example:
290 |
291 | >>> # solve poisson equation for trig function with a frequency of 3
292 | >>> hartree_plotter(3, True)
293 |
294 | .. math::
295 |
296 | n(x) = \sin^2(2\pi(3)x)
297 |
298 | \\nabla^2 V(x) = -4\pi n(x) \\xrightarrow[\\text{linalg.cg}]{\\text{scipy}} V(x)
299 |
300 | .. image:: figures/1D_hartree.png
301 |
302 | '''
303 | # Data for density and Ax=b solver
304 | nx = 300
305 | x = np.linspace(0, np.pi, nx)
306 | y = np.sin(freq*x)**2
307 | diag1x = np.ones(nx)/(x[1])
308 | D1x = sparse.spdiags(np.array([-diag1x, diag1x]), np.array([0,1]), nx, nx)
309 | diagx = np.ones(nx)/(x[1]**2)
310 | D2x = sparse.spdiags(np.array([diagx, -2*diagx, diagx]), np.array([-1,0,1]), nx, nx)
311 | T = -1/2 * D2x
312 | test = sparse.linalg.cg(-2*T, -4.*np.pi*y) # solve for V
313 |
314 | # LaTeX equation
315 | equation1 = '## $n(x) = \sin^2( 2 \pi ' + f'({freq})' + 'x)$'
316 | equation2 = '## $\\nabla^2V(x)=-4\pi n(x) \\xrightarrow[\\text{linalg.cg}]{\\text{scipy}} V(x)$'
317 | display(Markdown(equation1))
318 | display(Markdown(equation2))
319 |
320 | # Matplotlib Diagram
321 | plt.gcf()
322 | plt.clf()
323 | plt.title('1D Hartree Potential', size=15)
324 | plt.plot(x, y, label=f'n(x) = $\sin^2(2 \pi ({freq}) x)$')
325 | if solve_poisson:
326 | plt.plot(x, test[0], label='V(x)')
327 | plt.xlabel('x', size=15)
328 | plt.ylabel('Amplitude', size=15)
329 | plt.legend(loc='upper right')
330 | plt.grid()
331 | plt.show()
332 | print() # a little extra space
333 |
334 | def hamiltonian_display(functional, har, ex, cor):
335 | '''Generates LaTeX equation of effective single-particle Hamiltonian.
336 |
337 | :param str functional: XC potential (LDA or PBE)
338 | :param bool har: Hartree potential on or off
339 | :param bool ex: exchange term on or off
340 | :param bool cor: correlation term on or off
341 |
342 | Example:
343 |
344 | >>> # display effective hamiltonian with exch and hartree and without correlation
345 | >>> hamiltonian_display('LDA', True, True, False)
346 |
347 | .. math::
348 |
349 | \hat{h}_i = \hat{T}_{\\text{kin},i} + v_{\\text{Ha}}(n(r)) + v_{\\text{X}}^{\\text{LDA}}(n(r)) + 0
350 |
351 | '''
352 | ham = '## $$ \hat{h}_i = \hat{T}_{\\text{kin}, i} +'
353 | if har == True:
354 | ham += 'v_{\\text{Ha}}(n(r))'
355 | else:
356 | ham += '0'
357 | if ex == True:
358 | if functional == 'LDA':
359 | ham += '+ v_{\\text{X}}^{\\text{LDA}}(n(r))'
360 | elif functional == 'PBE':
361 | ham += '+ v_{\\text{X}}^{\\text{PBE}}(n(r), \\nabla n(r))'
362 | else:
363 | ham += '+ 0'
364 | if cor == True:
365 | if functional == 'LDA':
366 | ham += '+ v_{\\text{C}}^{\\text{LDA}}(n(r))'
367 | elif functional == 'PBE':
368 | ham += '+ v_{\\text{C}}^{\\text{PBE}}(n(r), \\nabla n(r))'
369 | else:
370 | ham += '+ 0'
371 | ham += '$$'
372 | display(Markdown(ham))
373 |
374 | def energy_plot(den_log, ener_log, converge_state, show_fig=True, save_fig=False, filename=None):
375 | '''Generates iteration number vs total energy plot.
376 |
377 | :param list den_log: list of numpy arrays with electron density
378 | :param list ener_log: list of total energy values in Ha
379 | :param bool converge_state: SCF loop ended in a converged (True) or unconverged (False) state
380 | :param bool show_fig: display figure to output (True) or not (False)
381 | :param bool save_fig: save a .png file of diagram
382 | :param str filename: filename to save to (w/o extension)
383 | '''
384 | fig = plt.figure(figsize=(6, 4))
385 | ax = fig.add_subplot(1, 1, 1)
386 | plt.title('Convergence Plot')
387 | plt.scatter(0, energy_log[0], color='#F97306', label='noninter Energy')
388 | plt.plot(np.arange(1,len(energy_log[1:]) + 1), energy_log[1:], 'o-', label='DFT Energy')
389 | plt.legend(loc='upper right')
390 | plt.text(0.785, 0.800, f'iterations: {len(density_log) - 1}',
391 | horizontalalignment='center', verticalalignment='center',
392 | transform=ax.transAxes)
393 | plt.text(0.785, 0.730,f' energy (Ha): {energy_log[-1]:.5f}',
394 | size=10.5, horizontalalignment='center', verticalalignment='center',
395 | transform= ax.transAxes)
396 | if converge_state == True:
397 | plt.text(0.79, 0.660,f'converged',
398 | size=10.5, horizontalalignment='center', verticalalignment='center',
399 | transform= ax.transAxes, weight='bold')
400 | elif converge_state == False:
401 | plt.text(0.79, 0.660,f'unconverged',
402 | size=10.5, horizontalalignment='center', verticalalignment='center',
403 | transform= ax.transAxes, weight='bold')
404 | plt.ylabel('Energy (Ha)')
405 | plt.xlabel('iteration #')
406 | plt.tight_layout()
407 | if save_fig:
408 | plt.savefig(f'{filename}.png', dpi = 800)
409 | if show_fig:
410 | plt.show()
411 | else:
412 | plt.close()
413 |
414 | def hard_walls(potential, nx, ny, nz):
415 | '''Sets potential of outermost grid points to 1,000. Used for testing.
416 |
417 | :param np.array potential: KS (or other) potential as a flattened array
418 | :param int nx: Grid points in x-direction.
419 | :param int ny: Grid points in y-direction.
420 | :param int nz: Grid points in z-direction.
421 | :return: Flattened array of grid points.
422 | '''
423 | potential = potential.reshape(nx, ny, nz)
424 | potential[0,:,:] = 1000
425 | potential[-1,:,:] = 1000
426 | potential[:,0,:] = 1000
427 | potential[:,-1,:] = 1000
428 | potential[:,:,0] = 1000
429 | potential[:,:,-1] = 1000
430 | return potential.flatten()
431 |
432 | def hartree(den, kin_oper, dx, dy, dz, nx, ny, nz):
433 | '''Uses Poisson's equation to find Hartree potential from density.
434 |
435 | :param np.array den: Electron density.
436 | :param np.array kin_oper: Kinetic energy operator as sparse matrix.
437 | :param float dx: Differential volume element in x-direction.
438 | :param float dy: Idem for y.
439 | :param float dz: Idem for z.
440 | :param int nx: Number of grid points in x-direction.
441 | :param int ny: Idem for y.
442 | :param int nz: Idem for z.
443 | :return:
444 | - **v_ha_flat** (*np.array*): Hartree potential as flattened array.
445 | - **v_ha_ener** (*float*): Hartree energy (in Ha units); needed to compute total energy
446 | '''
447 | clean_den = np.ma.array(den, mask= abs(den) < 0.000001) # Mask the low-density points
448 | clean_den = np.ma.filled(clean_den, fill_value=0.0) # Fill with zeros
449 | den = clean_den
450 | den = edge_cleaner(den, nx, ny, nz, num_edges=1) # Set edges to zero
451 | v_ha_flat = sparse.linalg.cg(-2*kin_oper,-4.*np.pi*den)[0]
452 | v_ha_flat = edge_cleaner(v_ha_flat, nx, ny, nz, num_edges=1)
453 | v_ha_ener = (1/2)*integ_3d(v_ha_flat*den, dx, dy, dz)
454 | return v_ha_flat, v_ha_ener
455 |
456 | def lda_exchange(den, dx, dy, dz):
457 | '''Finds LDA exchange potential and energy from density.
458 |
459 | :param np.array den: Electron density.
460 | :param float dx: Differential volume element in x-direction.
461 | :param float dy: Idem for y.
462 | :param float dz: Idem for z.
463 | :return:
464 | - **exch_pot_lda** (*np.array*): LDA exchange potential.
465 | - **exch_ener_lda** (*float*): LDA exchnage energy (in Ha).
466 | '''
467 | exch_pot_lda = -(3/4)*(3/np.pi)**(1/3)*(den)**(1/3)
468 | clean_den = np.ma.array(den, mask= abs(den) < 0.000001)
469 | clean_den = np.ma.filled(clean_den, fill_value=0.0)
470 | den = clean_den
471 | exch_ener_lda = -(3/4)*(3/np.pi)**(1/3)*integ_3d(den**(4/3), dx, dy, dz)
472 | return exch_pot_lda, exch_ener_lda
473 |
474 | def lda_correlation(den, dx, dy, dz):
475 | '''Finds LDA correlation potential and energy from density.
476 |
477 | :param np.array den: Electron density.
478 | :param float dx: Differential volume element in x-direction.
479 | :param float dy: Idem for y.
480 | :param float dz: Idem for z.
481 | :return:
482 | - **corr_pot** (*np.array*): LDA correlation potential.
483 | - **corr_en** (*float*): LDA correlation energy (in Ha).
484 | '''
485 | # a, b, c fitting constants used in paper: 10.1063/1.4958669
486 | a, b, c = (np.log(2)-1)/(2*np.pi**2), 20.4562557, (4*np.pi/3)**(1/3)
487 | corr_pot = a*np.log(1 + b*c*den**(1/3) + b*(c**2)*den**(2/3))
488 | clean_den = np.ma.array(den, mask= abs(den) < 0.000001)
489 | clean_den = np.ma.filled(clean_den, fill_value=0.0)
490 | den = clean_den
491 | corr_en = integ_3d(den*corr_pot, dx, dy, dz)
492 | return corr_pot, corr_en
493 |
494 | def RDG(den, der_1st, nx, ny, nz):
495 | '''Finds dimensionless reduced density gradient (needed for PBE exchange).
496 |
497 | :param np.array den: Electron density.
498 | :param np.array der_1st: 1st derivative operator as sparse matrix.
499 | :param int nx: Number of grid points in x-direction.
500 | :param int ny: Idem for y.
501 | :param int nz: Idem for z.
502 | :return:
503 | - **RDG** (*np.array*): Reduced density gradient as matrix.
504 | '''
505 | clean_den = np.ma.array(den, mask = abs(den) < 0.000001)
506 | den = clean_den
507 | RDG = (2*3**(1/3)*np.pi**(2/3))**(-1) * abs(der_1st.dot(den)) * den**(-4/3)
508 | RDG = edge_cleaner(RDG, nx, ny, nz, num_edges=1) #set the edges to zero
509 | RDG = np.ma.filled(RDG, fill_value=0.0) # return zeros where density is very small
510 | return RDG
511 |
512 | def pbe_exchange(den, D1st, dx, dy, dz, nx, ny, nz):
513 | '''Finds PBE exchange potential and energy from density + gradient.
514 |
515 | :param np.array den: Electron density.
516 | :param np.array D1st: 1st derivative operator as sparse matrix.
517 | :param float dx: Differential volume element in x-direction.
518 | :param float dy: Idem for y.
519 | :param float dz: Idem for z.
520 | :param int nx: Number of grid points in x-direction.
521 | :param int ny: Idem for y.
522 | :param int nz: Idem for z.
523 | :return:
524 | - **exch_pot_pbe** (*np.array*): PBE exchange potential.
525 | - **exch_ener_pbe** (*float*): PBE exchange energy (in Ha).
526 | '''
527 | # kappa and mu constants used in the paper, 10.1103/PhysRevLett.77.3865
528 | kappa, mu = 0.804, 0.2195149727645171
529 | s = RDG(den, D1st, nx, ny, nz)
530 | F_xs = 1 + kappa - kappa * (1 + mu * s**2 / kappa)**(-1) # exch enhancement factor
531 | exch_pot_pbe = F_xs * -(3/4)*(3/np.pi)**(1/3)*((den)**(1/3))
532 | clean_den = np.ma.array(den, mask= abs(den) < 0.000001)
533 | clean_den = np.ma.filled(clean_den, fill_value=0.0)
534 | den = clean_den
535 | exch_ener_pbe = integ_3d(den*exch_pot_pbe, dx, dy, dz)
536 | return exch_pot_pbe, exch_ener_pbe
537 |
538 | def cor_den_grad(den, der_1st, nx, ny, nz):
539 | '''Finds dimensionless correlation density gradient (used in PBE correlation).
540 |
541 | :param np.array den: Electron density.
542 | :param np.array der_1st: 1st derivative operator as sparse matrix.
543 | :param int nx: Number of grid points in x-direction.
544 | :param int ny: Idem for y.
545 | :param int nz: Idem for z.
546 | :return:
547 | - **t** (*np.array*): Correlation density gradient.
548 | '''
549 | d_g = abs(der_1st.dot(den))
550 | clean_den = np.ma.array(den, mask = abs(den) < 0.000001)
551 | den = clean_den
552 | t = (d_g*np.pi**(1/6))/(4*3**(1/6)*den**(7/6))
553 | t = edge_cleaner(t, nx, ny, nz, num_edges=1)
554 | t = np.ma.filled(t, fill_value=0.0)
555 | return t
556 |
557 | def pbe_correlation(den, der_1st, dx, dy, dz, nx, ny, nz):
558 | '''Finds PBE correlation potential and energy from density + gradient.
559 |
560 | :param np.array den: Electron density.
561 | :param np.array der_1st: 1st derivative operator as sparse matrix.
562 | :param float dx: Differential volume element in x-direction.
563 | :param float dy: Idem for y.
564 | :param float dz: Idem for z.
565 | :param int nx: Number of grid points in x-direction.
566 | :param int ny: Idem for y.
567 | :param int nz: Idem for z.
568 | :return:
569 | - **cor_pot_pbe** (*np.array*): PBE correlation potential.
570 | - **cor_ener_pbe** (*float*): PBE correlation energy (in Ha).
571 | '''
572 | lda_c_pot = lda_correlation(den, dx, dy, dz)[0]
573 | # beta and gamma constants used in the paper, 10.1103/PhysRevLett.77.3865
574 | beta, gamma = 0.06672455060314922, 0.031090690869654894
575 | lda_c_pot = np.ma.array(lda_c_pot, mask = abs(lda_c_pot) < 0.000001)
576 | A = (beta/gamma)*((np.exp(-lda_c_pot/gamma)-1)**(-1))
577 | t = cor_den_grad(den, der_1st, nx, ny, nz)
578 | H = gamma*np.log(1+(beta/gamma)*t**2*((1+A*(t**2))/(1+A*(t**2)+(A**2)*(t**4))))
579 | cor_pot_pbe = lda_c_pot + H
580 | cor_ener_pbe = integ_3d(den*cor_pot_pbe, dx, dy, dz)
581 | return cor_pot_pbe, cor_ener_pbe
582 |
583 |
--------------------------------------------------------------------------------
/notebooks/notebook_functions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tjz21/DFT_PIB_Code/ae44cb9c8968be26f1a821c0a18764e682046bcb/notebooks/notebook_functions/__init__.py
--------------------------------------------------------------------------------
/offline_jupyter/README.md:
--------------------------------------------------------------------------------
1 | # Offline Jupyter Notebooks
2 | Instead of using Google Colab, offline versions of the notebooks have been made available in this directory and can be run through the conventional Jupyter IDE. Instructions for setting up the conda virtual environment are provided here and have been tested on wsl-Ubuntu 22.04 using the Firefox browser. Although NB1 works fine here, **this is not the recommended approach and most of the GUI features in NB2 and NB3 do not display correctly** (because of differences in how ipywidgets runs on Colab vs a local Jupyter Notebook).
3 | ## Conda Instructions
4 | 1. Create a conda virtual environment
5 | ```
6 | conda create --name DFT_code python=3.10
7 | ```
8 | 2. Install dependencies to this environment from the requirements.txt file. This takes ~4 minutes to complete.
9 | ```sh
10 | conda activate DFT_code
11 | ```
12 | ```sh
13 | pip install -r requirements.txt
14 | ```
15 | 3. Launch a Jupyter notebook instance.
16 | ```sh
17 | jupyter notebook
18 | ```
19 | 4. Download the offline versions of the notebooks and open them through the Jupyter IDE. The visualizations should become active after executing the code cells.
20 |
--------------------------------------------------------------------------------
/offline_jupyter/old_ipv_compatible/info.txt:
--------------------------------------------------------------------------------
1 | These are the old versions of the offline notebooks made to work with ipyvolume instead of plotly.
2 |
--------------------------------------------------------------------------------
/offline_jupyter/old_ipv_compatible/requirements.txt:
--------------------------------------------------------------------------------
1 | ipyvolume==0.6.0
2 | rdkit==2023.3.1
3 | py3Dmol==2.0.3
4 | pyscf==2.2.1
5 | pythreejs==2.4.2
6 | Cube-Toolz @ git+https://github.com/funkymunkycool/Cube-Toolz.git@915ccf277a25e1c6996365954db592ab39855f6a
7 | numpy==1.22.4
8 | matplotlib==3.7.1
9 | ipython==7.34.0
10 | ipywidgets==8.0.7
11 | termcolor==2.2.0
12 | pathlib==1.0.1
13 | pandas==1.5.3
14 | jupyter
15 |
--------------------------------------------------------------------------------
/offline_jupyter/requirements.txt:
--------------------------------------------------------------------------------
1 | plotly==5.13.1
2 | kaleido
3 | rdkit==2023.3.1
4 | py3Dmol==2.0.3
5 | pyscf==2.2.1
6 | pythreejs==2.4.2
7 | Cube-Toolz @ git+https://github.com/funkymunkycool/Cube-Toolz.git@915ccf277a25e1c6996365954db592ab39855f6a
8 | numpy==1.22.4
9 | matplotlib==3.7.1
10 | ipython==7.34.0
11 | ipywidgets==8.0.7
12 | termcolor==2.2.0
13 | pathlib==1.0.1
14 | pandas==1.5.3
15 | jupyter==1.0.0
16 |
--------------------------------------------------------------------------------