Spaces:
Running
Running
Upload Molecular_Integrals.py
Browse files- src/pages/Molecular_Integrals.py +899 -0
src/pages/Molecular_Integrals.py
ADDED
|
@@ -0,0 +1,899 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import os
|
| 3 |
+
import tempfile
|
| 4 |
+
import numpy as np
|
| 5 |
+
import py3Dmol
|
| 6 |
+
import streamlit.components.v1 as components
|
| 7 |
+
from io import StringIO
|
| 8 |
+
import time
|
| 9 |
+
from ase import Atoms
|
| 10 |
+
from ase.io import read as ase_read
|
| 11 |
+
import pandas as pd
|
| 12 |
+
import plotly.graph_objects as go
|
| 13 |
+
import plotly.express as px
|
| 14 |
+
from plotly.subplots import make_subplots
|
| 15 |
+
|
| 16 |
+
@st.cache_data
|
| 17 |
+
def load_4c2e_eri(_basis):
|
| 18 |
+
ERI = Integrals.rys_4c2e_symm(basis)
|
| 19 |
+
return ERI
|
| 20 |
+
|
| 21 |
+
# Set page configuration
|
| 22 |
+
st.set_page_config(
|
| 23 |
+
page_title='PyFock GUI - Molecular Integrals',
|
| 24 |
+
layout='wide',
|
| 25 |
+
page_icon="⚛️",
|
| 26 |
+
menu_items={
|
| 27 |
+
'About': "PyFock GUI - A web interface for PyFock, a pure Python DFT code with Numba JIT acceleration"
|
| 28 |
+
}
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
# === Background video styling ===
|
| 32 |
+
def set_css():
|
| 33 |
+
st.markdown("""
|
| 34 |
+
<style>
|
| 35 |
+
#myVideo {
|
| 36 |
+
position: fixed;
|
| 37 |
+
right: 0;
|
| 38 |
+
bottom: 0;
|
| 39 |
+
min-width: 100%;
|
| 40 |
+
min-height: 100%;
|
| 41 |
+
opacity: 0.12;
|
| 42 |
+
pointer-events: none;
|
| 43 |
+
}
|
| 44 |
+
.content {
|
| 45 |
+
position: fixed;
|
| 46 |
+
bottom: 0;
|
| 47 |
+
background: rgba(0, 0, 0, 0.5);
|
| 48 |
+
color: #f1f1f1;
|
| 49 |
+
width: 100%;
|
| 50 |
+
padding: 20px;
|
| 51 |
+
}
|
| 52 |
+
</style>
|
| 53 |
+
""", unsafe_allow_html=True)
|
| 54 |
+
|
| 55 |
+
def embed_video():
|
| 56 |
+
video_link = "https://raw.githubusercontent.com/manassharma07/Website_Files_for_PyFock/main/background_video_pyfock.mp4"
|
| 57 |
+
st.sidebar.markdown(f"""
|
| 58 |
+
<video autoplay muted loop id="myVideo">
|
| 59 |
+
<source src="{video_link}">
|
| 60 |
+
Your browser does not support HTML5 video.
|
| 61 |
+
</video>
|
| 62 |
+
""", unsafe_allow_html=True)
|
| 63 |
+
|
| 64 |
+
set_css()
|
| 65 |
+
embed_video()
|
| 66 |
+
|
| 67 |
+
# Sidebar with enhanced styling (same as home page)
|
| 68 |
+
st.sidebar.image("https://raw.githubusercontent.com/manassharma07/PyFock/main/logo_crysx_pyfock.png", use_container_width=True)
|
| 69 |
+
st.sidebar.markdown("---")
|
| 70 |
+
|
| 71 |
+
st.sidebar.markdown("### About PyFock")
|
| 72 |
+
st.sidebar.markdown("""
|
| 73 |
+
**Pure Python DFT** with performance matching C++ codes!
|
| 74 |
+
|
| 75 |
+
**Key Advantages:**
|
| 76 |
+
- 100% Pure Python (including molecular integrals)
|
| 77 |
+
- Numba JIT acceleration
|
| 78 |
+
- GPU support (CUDA via CuPy)
|
| 79 |
+
- Near-quadratic scaling (~O(N²·⁰⁵))
|
| 80 |
+
- Accuracy matching PySCF (<10⁻⁷ Ha)
|
| 81 |
+
- Windows/Linux/MacOS compatible
|
| 82 |
+
- Easy pip installation
|
| 83 |
+
""")
|
| 84 |
+
|
| 85 |
+
st.sidebar.markdown("---")
|
| 86 |
+
|
| 87 |
+
st.sidebar.markdown("### GUI Features")
|
| 88 |
+
st.sidebar.markdown("""
|
| 89 |
+
* Run DFT in your browser
|
| 90 |
+
* Visualize HOMO, LUMO, density
|
| 91 |
+
* Compare with PySCF
|
| 92 |
+
* Download cube files & scripts
|
| 93 |
+
* Interactive 3D visualization
|
| 94 |
+
* Calculate molecular integrals
|
| 95 |
+
* No installation required!
|
| 96 |
+
""")
|
| 97 |
+
|
| 98 |
+
st.sidebar.markdown("---")
|
| 99 |
+
|
| 100 |
+
st.sidebar.markdown("### 🔗 Links & Resources")
|
| 101 |
+
st.sidebar.markdown("""
|
| 102 |
+
[](https://github.com/manassharma07/PyFock)
|
| 103 |
+
[](https://github.com/manassharma07/PyFock-GUI)
|
| 104 |
+
[](https://pypi.org/project/pyfock/)
|
| 105 |
+
[](https://pyfock-docs.bragitoff.com)
|
| 106 |
+
|
| 107 |
+
📄 **Article:** *(coming soon)*
|
| 108 |
+
|
| 109 |
+
👨💻 **Developer:** [Manas Sharma](https://www.linkedin.com/in/manassharma07)
|
| 110 |
+
|
| 111 |
+
⭐ **Star the repo** if you find it useful!
|
| 112 |
+
""")
|
| 113 |
+
|
| 114 |
+
st.sidebar.markdown("---")
|
| 115 |
+
|
| 116 |
+
with st.sidebar.expander("📦 Installation Instructions for PyFock"):
|
| 117 |
+
st.code("""
|
| 118 |
+
# Install LibXC — required by PyFock
|
| 119 |
+
# For Python < 3.10:
|
| 120 |
+
sudo apt-get install libxc-dev # Ubuntu/Debian
|
| 121 |
+
pip install pylibxc2
|
| 122 |
+
|
| 123 |
+
# For Python >= 3.10:
|
| 124 |
+
conda install -c conda-forge pylibxc -y
|
| 125 |
+
|
| 126 |
+
# Install PyFock
|
| 127 |
+
pip install pyfock
|
| 128 |
+
|
| 129 |
+
# Optional: GPU support
|
| 130 |
+
pip install cupy-cuda12x
|
| 131 |
+
""", language="bash")
|
| 132 |
+
|
| 133 |
+
st.sidebar.markdown("---")
|
| 134 |
+
|
| 135 |
+
with st.sidebar.expander("⚡ Performance Highlights"):
|
| 136 |
+
st.markdown("""
|
| 137 |
+
**CPU Performance:**
|
| 138 |
+
- Upto 2x faster than PySCF
|
| 139 |
+
- Strong scaling up to 32 cores
|
| 140 |
+
- ~O(N²·⁰⁵) scaling with basis functions
|
| 141 |
+
- Suitable for large systems (upto ~10,000 basis functions)
|
| 142 |
+
|
| 143 |
+
**GPU Acceleration:**
|
| 144 |
+
- Up to **14× speedup** on A100 GPU vs 4-core CPU
|
| 145 |
+
- Single A100 GPU handles 4000+ basis functions
|
| 146 |
+
- Consumer GPUs (RTX series) supported
|
| 147 |
+
""")
|
| 148 |
+
|
| 149 |
+
st.sidebar.markdown("---")
|
| 150 |
+
st.sidebar.markdown("*Made with PyFock by PhysWhiz*")
|
| 151 |
+
st.sidebar.markdown("*Pure Python • Numba JIT • GPU Ready*")
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# Initialize session state
|
| 155 |
+
if 'results' not in st.session_state:
|
| 156 |
+
st.session_state.results = None
|
| 157 |
+
if 'timings' not in st.session_state:
|
| 158 |
+
st.session_state.timings = None
|
| 159 |
+
if 'n_basis' not in st.session_state:
|
| 160 |
+
st.session_state.n_basis = None
|
| 161 |
+
if 'calculation_done' not in st.session_state:
|
| 162 |
+
st.session_state.calculation_done = False
|
| 163 |
+
# Helper functions
|
| 164 |
+
def _parse_xyz_to_atoms(xyz_text):
|
| 165 |
+
return ase_read(StringIO(xyz_text), format='xyz')
|
| 166 |
+
|
| 167 |
+
def get_structure_viz2(atoms_obj, style='stick', width=400, height=400):
|
| 168 |
+
xyz_str = ""
|
| 169 |
+
xyz_str += f"{len(atoms_obj)}\n"
|
| 170 |
+
xyz_str += "Structure\n"
|
| 171 |
+
for atom in atoms_obj:
|
| 172 |
+
sym = atom.symbol if hasattr(atom, 'symbol') else atom.get_chemical_symbols()[0]
|
| 173 |
+
pos = atom.position if hasattr(atom, 'position') else atom.position
|
| 174 |
+
xyz_str += f"{sym} {pos[0]:.6f} {pos[1]:.6f} {pos[2]:.6f}\n"
|
| 175 |
+
view = py3Dmol.view(width=width, height=height)
|
| 176 |
+
view.addModel(xyz_str, "xyz")
|
| 177 |
+
if style.lower() == 'ball-stick':
|
| 178 |
+
view.setStyle({'stick': {'radius': 0.2}, 'sphere': {'scale': 0.3}})
|
| 179 |
+
elif style.lower() == 'stick':
|
| 180 |
+
view.setStyle({'stick': {}})
|
| 181 |
+
elif style.lower() == 'ball':
|
| 182 |
+
view.setStyle({'sphere': {'scale': 0.4}})
|
| 183 |
+
else:
|
| 184 |
+
view.setStyle({'stick': {'radius': 0.15}})
|
| 185 |
+
view.zoomTo()
|
| 186 |
+
view.setBackgroundColor('white')
|
| 187 |
+
return view
|
| 188 |
+
|
| 189 |
+
# Example molecules (same as home page)
|
| 190 |
+
EXAMPLE_MOLECULES = {
|
| 191 |
+
"Water": """3
|
| 192 |
+
Water molecule
|
| 193 |
+
O 0.000000 0.000000 0.117790
|
| 194 |
+
H 0.000000 0.755453 -0.471161
|
| 195 |
+
H 0.000000 -0.755453 -0.471161""",
|
| 196 |
+
|
| 197 |
+
"Acetone": """10
|
| 198 |
+
Acetone molecule
|
| 199 |
+
O 0.000247197289657 -1.311344859924947 0.000033372829371
|
| 200 |
+
C 0.000008761532627 -0.103796835732344 0.000232428229233
|
| 201 |
+
C 1.285011287026515 0.689481114475586 -0.000005118102586
|
| 202 |
+
C -1.285310305026895 0.688972899031773 -0.000008308924344
|
| 203 |
+
H 1.326033303131908 1.335179621002258 -0.879574382138206
|
| 204 |
+
H 1.324106820690737 1.339904097018082 0.876116213728557
|
| 205 |
+
H 2.136706543431990 0.014597477886500 0.002551051783708
|
| 206 |
+
H -2.136748467815155 0.013761611436540 0.002550873429523
|
| 207 |
+
H -1.326572603227431 1.334639056170679 -0.879590243114624
|
| 208 |
+
H -1.324682534264540 1.339405821389821 0.876094109485741""",
|
| 209 |
+
|
| 210 |
+
"Methane": """5
|
| 211 |
+
Methane molecule
|
| 212 |
+
C 0.000000 0.000000 0.000000
|
| 213 |
+
H 0.629118 0.629118 0.629118
|
| 214 |
+
H -0.629118 -0.629118 0.629118
|
| 215 |
+
H -0.629118 0.629118 -0.629118
|
| 216 |
+
H 0.629118 -0.629118 -0.629118""",
|
| 217 |
+
|
| 218 |
+
"Benzene": """12
|
| 219 |
+
Benzene molecule
|
| 220 |
+
C 1.395890 0.000000 0.000000
|
| 221 |
+
C 0.697945 1.209021 0.000000
|
| 222 |
+
C -0.697945 1.209021 0.000000
|
| 223 |
+
C -1.395890 0.000000 0.000000
|
| 224 |
+
C -0.697945 -1.209021 0.000000
|
| 225 |
+
C 0.697945 -1.209021 0.000000
|
| 226 |
+
H 2.482610 0.000000 0.000000
|
| 227 |
+
H 1.241305 2.149540 0.000000
|
| 228 |
+
H -1.241305 2.149540 0.000000
|
| 229 |
+
H -2.482610 0.000000 0.000000
|
| 230 |
+
H -1.241305 -2.149540 0.000000
|
| 231 |
+
H 1.241305 -2.149540 0.000000""",
|
| 232 |
+
|
| 233 |
+
"Ammonia": """4
|
| 234 |
+
Ammonia molecule
|
| 235 |
+
N 0.000000 0.000000 0.100000
|
| 236 |
+
H 0.945000 0.000000 -0.266000
|
| 237 |
+
H -0.472500 0.818000 -0.266000
|
| 238 |
+
H -0.472500 -0.818000 -0.266000""",
|
| 239 |
+
|
| 240 |
+
"Carbon Dioxide": """3
|
| 241 |
+
Carbon dioxide molecule
|
| 242 |
+
C 0.000000 0.000000 0.000000
|
| 243 |
+
O 0.000000 0.000000 1.160000
|
| 244 |
+
O 0.000000 0.000000 -1.160000""",
|
| 245 |
+
|
| 246 |
+
"Hydrogen Peroxide": """4
|
| 247 |
+
Hydrogen peroxide molecule
|
| 248 |
+
O 0.000000 0.000000 0.000000
|
| 249 |
+
O 1.450000 0.000000 0.000000
|
| 250 |
+
H 0.000000 0.930000 0.000000
|
| 251 |
+
H 1.450000 -0.930000 0.000000""",
|
| 252 |
+
|
| 253 |
+
"Formaldehyde": """4
|
| 254 |
+
Formaldehyde molecule
|
| 255 |
+
C 0.000000 0.000000 0.000000
|
| 256 |
+
O 1.200000 0.000000 0.000000
|
| 257 |
+
H -0.550000 0.940000 0.000000
|
| 258 |
+
H -0.550000 -0.940000 0.000000""",
|
| 259 |
+
|
| 260 |
+
"Hydrogen Sulfide": """3
|
| 261 |
+
Hydrogen sulfide molecule
|
| 262 |
+
S 0.000000 0.000000 0.000000
|
| 263 |
+
H 0.960000 0.000000 0.000000
|
| 264 |
+
H -0.480000 0.830000 0.000000""",
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
BASIS_SETS = ["sto-3g", "sto-6g", "3-21G", "4-31G", "6-31G", "6-31+G", "6-31++G", "cc-pvDZ", "def2-SVP", "def2-TZVP"]
|
| 268 |
+
AUXBASIS_SETS = ["def2-universal-jkfit", "def2-universal-jfit", "sto-3g", "def2-SVP", "6-31G"]
|
| 269 |
+
|
| 270 |
+
# Main title
|
| 271 |
+
st.title("🧮 PyFock - Molecular Integrals Calculator")
|
| 272 |
+
st.markdown("""
|
| 273 |
+
Explore the fundamental quantum mechanical integrals that form the basis of electronic structure calculations.
|
| 274 |
+
Learn how overlap, kinetic, nuclear attraction, and electron repulsion integrals are computed!
|
| 275 |
+
""")
|
| 276 |
+
st.markdown("---")
|
| 277 |
+
|
| 278 |
+
# Input section
|
| 279 |
+
st.header("1. System Setup")
|
| 280 |
+
|
| 281 |
+
col1, col2 = st.columns([1.3, 1])
|
| 282 |
+
|
| 283 |
+
with col1:
|
| 284 |
+
st.subheader("Molecule Input")
|
| 285 |
+
|
| 286 |
+
molecule_choice = st.selectbox(
|
| 287 |
+
"Select example molecule or paste custom XYZ:",
|
| 288 |
+
[
|
| 289 |
+
"Water",
|
| 290 |
+
"Acetone",
|
| 291 |
+
"Methane",
|
| 292 |
+
"Benzene",
|
| 293 |
+
"Ammonia",
|
| 294 |
+
"Carbon Dioxide",
|
| 295 |
+
"Hydrogen Peroxide",
|
| 296 |
+
"Formaldehyde",
|
| 297 |
+
"Hydrogen Sulfide",
|
| 298 |
+
"Custom"
|
| 299 |
+
]
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
if molecule_choice == "Custom":
|
| 303 |
+
xyz_content = st.text_area(
|
| 304 |
+
"Paste XYZ coordinates:",
|
| 305 |
+
height=200,
|
| 306 |
+
placeholder="3\nWater molecule\nO 0.0 0.0 0.0\nH 0.757 0.586 0.0\nH -0.757 0.586 0.0"
|
| 307 |
+
)
|
| 308 |
+
else:
|
| 309 |
+
xyz_content = st.text_area(
|
| 310 |
+
"XYZ coordinates:",
|
| 311 |
+
value=EXAMPLE_MOLECULES[molecule_choice],
|
| 312 |
+
height=200
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
with col2:
|
| 316 |
+
if xyz_content and xyz_content.strip():
|
| 317 |
+
st.markdown("### Molecule Visualization")
|
| 318 |
+
viz_style = st.selectbox("Select Visualization Style:", ["ball-stick", "stick", "ball"], key="viz_style_select")
|
| 319 |
+
atoms_obj = _parse_xyz_to_atoms(xyz_content)
|
| 320 |
+
|
| 321 |
+
view_3d = get_structure_viz2(atoms_obj, style=viz_style, width=400, height=400)
|
| 322 |
+
try:
|
| 323 |
+
st.components.v1.html(view_3d._make_html(), width=420, height=420)
|
| 324 |
+
except Exception:
|
| 325 |
+
t = view_3d.js()
|
| 326 |
+
html_content = f"{t.startjs}{t.endjs}"
|
| 327 |
+
components.html(html_content, height=420, width=420)
|
| 328 |
+
|
| 329 |
+
st.markdown("### Structure Information")
|
| 330 |
+
atoms_info = {
|
| 331 |
+
"Number of Atoms": len(atoms_obj),
|
| 332 |
+
"Chemical Formula": atoms_obj.get_chemical_formula() if hasattr(atoms_obj, 'get_chemical_formula') else "".join(atoms_obj.get_chemical_symbols()),
|
| 333 |
+
"Atom Types": ", ".join(sorted(list(set(atoms_obj.get_chemical_symbols()))))
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
for key, value in atoms_info.items():
|
| 337 |
+
st.write(f"**{key}:** {value}")
|
| 338 |
+
|
| 339 |
+
with col1:
|
| 340 |
+
st.subheader("Basis Set Configuration")
|
| 341 |
+
basis_set = st.selectbox("Basis Set:", BASIS_SETS, index=0)
|
| 342 |
+
auxbasis_set = st.selectbox("Basis Set:", AUXBASIS_SETS, index=0)
|
| 343 |
+
use_spherical = st.checkbox("Convert to Spherical AOs (SAO)", value=False,
|
| 344 |
+
help="Convert 1e integrals from Cartesian AOs (CAO) to Spherical AOs (SAO)")
|
| 345 |
+
|
| 346 |
+
st.markdown("---")
|
| 347 |
+
|
| 348 |
+
# Integral selection
|
| 349 |
+
st.header("2. Select Integrals to Calculate")
|
| 350 |
+
|
| 351 |
+
col3, col4 = st.columns(2)
|
| 352 |
+
|
| 353 |
+
with col3:
|
| 354 |
+
st.subheader("One-Electron Integrals")
|
| 355 |
+
calc_overlap = st.checkbox("Overlap Integrals (S)", value=True,
|
| 356 |
+
help="⟨φᵢ|φⱼ⟩ - Measures orbital overlap")
|
| 357 |
+
calc_kinetic = st.checkbox("Kinetic Energy Integrals (T)", value=True,
|
| 358 |
+
help="⟨φᵢ|-½∇²|φⱼ⟩ - Kinetic energy operator")
|
| 359 |
+
calc_nuclear = st.checkbox("Nuclear Attraction Integrals (V)", value=True,
|
| 360 |
+
help="⟨φᵢ|-Σ Zₐ/rₐ|φⱼ⟩ - Electron-nuclear attraction")
|
| 361 |
+
|
| 362 |
+
with col4:
|
| 363 |
+
st.subheader("Two-Electron Integrals")
|
| 364 |
+
calc_eri_4c2e = st.checkbox("4-Center 2-Electron (ERI)", value=False,
|
| 365 |
+
help="⟨φᵢφⱼ|1/r₁₂|φₖφₗ⟩ - Electron-electron repulsion")
|
| 366 |
+
|
| 367 |
+
# if calc_eri_4c2e:
|
| 368 |
+
# eri_algorithm = st.radio("Algorithm:", ["Rys Quadrature (Fast)", "Conventional (Slow)"],
|
| 369 |
+
# help="Rys quadrature is significantly faster for most systems")
|
| 370 |
+
|
| 371 |
+
calc_eri_3c2e = st.checkbox("3-Center 2-Electron (3c2e)", value=False,
|
| 372 |
+
help="Used in density fitting / RI approximations")
|
| 373 |
+
calc_eri_2c2e = st.checkbox("2-Center 2-Electron (2c2e)", value=False,
|
| 374 |
+
help="Auxiliary basis integrals for density fitting")
|
| 375 |
+
|
| 376 |
+
# Subset selection
|
| 377 |
+
# st.subheader("Optional: Calculate Subset of Matrix")
|
| 378 |
+
# use_subset = st.checkbox("Calculate only a subset of the integral matrix", value=False)
|
| 379 |
+
use_subset = False
|
| 380 |
+
# if use_subset:
|
| 381 |
+
# col5, col6, col7, col8 = st.columns(4)
|
| 382 |
+
# with col5:
|
| 383 |
+
# row_start = st.number_input("Row Start:", min_value=0, value=0, step=1)
|
| 384 |
+
# with col6:
|
| 385 |
+
# row_end = st.number_input("Row End:", min_value=1, value=5, step=1)
|
| 386 |
+
# with col7:
|
| 387 |
+
# col_start = st.number_input("Col Start:", min_value=0, value=0, step=1)
|
| 388 |
+
# with col8:
|
| 389 |
+
# col_end = st.number_input("Col End:", min_value=1, value=5, step=1)
|
| 390 |
+
|
| 391 |
+
st.markdown("---")
|
| 392 |
+
|
| 393 |
+
# Calculate button
|
| 394 |
+
# if st.button("🧮 Calculate Integrals", type="primary"):
|
| 395 |
+
|
| 396 |
+
if not xyz_content.strip():
|
| 397 |
+
st.error("Please provide XYZ coordinates!")
|
| 398 |
+
st.stop()
|
| 399 |
+
|
| 400 |
+
# Check if at least one integral type is selected
|
| 401 |
+
if not any([calc_overlap, calc_kinetic, calc_nuclear, calc_eri_4c2e, calc_eri_3c2e, calc_eri_2c2e]):
|
| 402 |
+
st.error("Please select at least one integral type to calculate!")
|
| 403 |
+
st.stop()
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
# Calculate button
|
| 407 |
+
if st.button("🧮 Calculate Integrals", type="primary", use_container_width=True):
|
| 408 |
+
progress_bar = st.progress(0)
|
| 409 |
+
status_text = st.empty()
|
| 410 |
+
try:
|
| 411 |
+
status_text.text("Importing PyFock modules...")
|
| 412 |
+
progress_bar.progress(10)
|
| 413 |
+
|
| 414 |
+
from pyfock import Basis, Mol, Integrals
|
| 415 |
+
|
| 416 |
+
status_text.text("Creating molecule object...")
|
| 417 |
+
progress_bar.progress(20)
|
| 418 |
+
|
| 419 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
|
| 420 |
+
f.write(xyz_content)
|
| 421 |
+
xyz_file = f.name
|
| 422 |
+
|
| 423 |
+
mol = Mol(coordfile=xyz_file)
|
| 424 |
+
|
| 425 |
+
status_text.text(f"Loading basis set: {basis_set}...")
|
| 426 |
+
progress_bar.progress(30)
|
| 427 |
+
|
| 428 |
+
basis = Basis(mol, {'all': Basis.load(mol=mol, basis_name=basis_set)})
|
| 429 |
+
auxbasis = Basis(mol, {'all': Basis.load(mol=mol, basis_name=auxbasis_set)})
|
| 430 |
+
|
| 431 |
+
n_basis = basis.bfs_nao
|
| 432 |
+
st.info(f"✓ System has {n_basis} basis functions")
|
| 433 |
+
if n_basis > 40:
|
| 434 |
+
st.error(f"❌ This system has {n_basis} basis functions, exceeding the limit of 40. Please use a smaller basis set or fewer atoms.")
|
| 435 |
+
os.unlink(xyz_file)
|
| 436 |
+
st.stop()
|
| 437 |
+
|
| 438 |
+
# Prepare results storage
|
| 439 |
+
results = {}
|
| 440 |
+
timings = {}
|
| 441 |
+
|
| 442 |
+
# Setup subset slice if needed
|
| 443 |
+
if use_subset:
|
| 444 |
+
subset_slice = [row_start, row_end, col_start, col_end]
|
| 445 |
+
else:
|
| 446 |
+
subset_slice = None
|
| 447 |
+
|
| 448 |
+
progress_step = 40
|
| 449 |
+
progress_increment = 50 / sum([calc_overlap, calc_kinetic, calc_nuclear,
|
| 450 |
+
calc_eri_4c2e, calc_eri_3c2e, calc_eri_2c2e])
|
| 451 |
+
|
| 452 |
+
# Calculate one-electron integrals
|
| 453 |
+
if calc_overlap:
|
| 454 |
+
status_text.text("Calculating overlap integrals...")
|
| 455 |
+
start = time.time()
|
| 456 |
+
if subset_slice:
|
| 457 |
+
S = Integrals.overlap_mat_symm(basis, slice=subset_slice)
|
| 458 |
+
else:
|
| 459 |
+
S = Integrals.overlap_mat_symm(basis)
|
| 460 |
+
|
| 461 |
+
if use_spherical:
|
| 462 |
+
c2sph_mat = basis.cart2sph_basis()
|
| 463 |
+
S = np.dot(c2sph_mat, np.dot(S, c2sph_mat.T))
|
| 464 |
+
|
| 465 |
+
results['Overlap'] = S
|
| 466 |
+
timings['Overlap'] = time.time() - start
|
| 467 |
+
progress_step += progress_increment
|
| 468 |
+
progress_bar.progress(int(progress_step))
|
| 469 |
+
|
| 470 |
+
if calc_kinetic:
|
| 471 |
+
status_text.text("Calculating kinetic energy integrals...")
|
| 472 |
+
start = time.time()
|
| 473 |
+
if subset_slice:
|
| 474 |
+
T = Integrals.kin_mat_symm(basis, slice=subset_slice)
|
| 475 |
+
else:
|
| 476 |
+
T = Integrals.kin_mat_symm(basis)
|
| 477 |
+
|
| 478 |
+
if use_spherical:
|
| 479 |
+
c2sph_mat = basis.cart2sph_basis()
|
| 480 |
+
T = np.dot(c2sph_mat, np.dot(T, c2sph_mat.T))
|
| 481 |
+
|
| 482 |
+
results['Kinetic'] = T
|
| 483 |
+
timings['Kinetic'] = time.time() - start
|
| 484 |
+
progress_step += progress_increment
|
| 485 |
+
progress_bar.progress(int(progress_step))
|
| 486 |
+
|
| 487 |
+
if calc_nuclear:
|
| 488 |
+
status_text.text("Calculating nuclear attraction integrals...")
|
| 489 |
+
start = time.time()
|
| 490 |
+
if subset_slice:
|
| 491 |
+
V = Integrals.nuc_mat_symm(basis, mol, slice=subset_slice)
|
| 492 |
+
else:
|
| 493 |
+
V = Integrals.nuc_mat_symm(basis, mol)
|
| 494 |
+
|
| 495 |
+
if use_spherical:
|
| 496 |
+
c2sph_mat = basis.cart2sph_basis()
|
| 497 |
+
V = np.dot(c2sph_mat, np.dot(V, c2sph_mat.T))
|
| 498 |
+
|
| 499 |
+
results['Nuclear'] = V
|
| 500 |
+
timings['Nuclear'] = time.time() - start
|
| 501 |
+
progress_step += progress_increment
|
| 502 |
+
progress_bar.progress(int(progress_step))
|
| 503 |
+
|
| 504 |
+
# Calculate two-electron integrals
|
| 505 |
+
if calc_eri_4c2e:
|
| 506 |
+
if n_basis > 25:
|
| 507 |
+
st.warning("⚠️ 4c2e integral calculation for more than 25 basis functions will be too slow for running on the cloud. Download the app and run on your system.")
|
| 508 |
+
calc_eri_4c2e = False
|
| 509 |
+
else:
|
| 510 |
+
status_text.text("Calculating 4c2e integrals (this may take a while)...")
|
| 511 |
+
start = time.time()
|
| 512 |
+
ERI = Integrals.rys_4c2e_symm(basis)
|
| 513 |
+
# ERI = load_4c2e_eri(basis)
|
| 514 |
+
timings['4c2e'] = time.time() - start
|
| 515 |
+
results['4c2e'] = ERI
|
| 516 |
+
|
| 517 |
+
progress_step += progress_increment
|
| 518 |
+
progress_bar.progress(int(progress_step))
|
| 519 |
+
|
| 520 |
+
if calc_eri_3c2e:
|
| 521 |
+
status_text.text("Calculating 3c2e integrals...")
|
| 522 |
+
start = time.time()
|
| 523 |
+
ERI_3c = Integrals.rys_3c2e_symm(basis, auxbasis)
|
| 524 |
+
results['3c2e'] = ERI_3c
|
| 525 |
+
timings['3c2e'] = time.time() - start
|
| 526 |
+
progress_step += progress_increment
|
| 527 |
+
progress_bar.progress(int(progress_step))
|
| 528 |
+
|
| 529 |
+
if calc_eri_2c2e:
|
| 530 |
+
status_text.text("Calculating 2c2e integrals...")
|
| 531 |
+
start = time.time()
|
| 532 |
+
ERI_2c = Integrals.rys_2c2e_symm(basis)
|
| 533 |
+
results['2c2e'] = ERI_2c
|
| 534 |
+
timings['2c2e'] = time.time() - start
|
| 535 |
+
progress_step += progress_increment
|
| 536 |
+
progress_bar.progress(int(progress_step))
|
| 537 |
+
|
| 538 |
+
progress_bar.progress(100)
|
| 539 |
+
status_text.text("✅ All calculations completed!")
|
| 540 |
+
# Store results in session state
|
| 541 |
+
st.session_state.results = results
|
| 542 |
+
st.session_state.timings = timings
|
| 543 |
+
st.session_state.calculation_done = True
|
| 544 |
+
except ImportError as e:
|
| 545 |
+
st.error(f"❌ Import Error: {str(e)}")
|
| 546 |
+
st.info("Make sure PyFock is installed: `pip install pyfock`")
|
| 547 |
+
progress_bar.empty()
|
| 548 |
+
status_text.empty()
|
| 549 |
+
except Exception as e:
|
| 550 |
+
st.error(f"❌ Calculation failed: {str(e)}")
|
| 551 |
+
import traceback
|
| 552 |
+
st.code(traceback.format_exc())
|
| 553 |
+
progress_bar.empty()
|
| 554 |
+
status_text.empty()
|
| 555 |
+
# Cleanup
|
| 556 |
+
os.unlink(xyz_file)
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
if 'xyz_file' in locals():
|
| 561 |
+
try:
|
| 562 |
+
os.unlink(xyz_file)
|
| 563 |
+
except:
|
| 564 |
+
pass
|
| 565 |
+
# Display results if calculation has been done
|
| 566 |
+
if st.session_state.calculation_done and st.session_state.results is not None:
|
| 567 |
+
|
| 568 |
+
results = st.session_state.results
|
| 569 |
+
timings = st.session_state.timings
|
| 570 |
+
st.success("✅ Integral calculations completed successfully!")
|
| 571 |
+
|
| 572 |
+
# Display results
|
| 573 |
+
st.header("3. Results")
|
| 574 |
+
|
| 575 |
+
# Timing summary
|
| 576 |
+
st.subheader("Computation Times")
|
| 577 |
+
timing_cols = st.columns(len(timings))
|
| 578 |
+
for idx, (name, timing) in enumerate(timings.items()):
|
| 579 |
+
with timing_cols[idx]:
|
| 580 |
+
st.metric(name, f"{timing:.4f} s")
|
| 581 |
+
|
| 582 |
+
st.markdown("---")
|
| 583 |
+
|
| 584 |
+
# Display each integral type
|
| 585 |
+
for integral_name, integral_matrix in results.items():
|
| 586 |
+
st.subheader(f"{integral_name} Integrals")
|
| 587 |
+
|
| 588 |
+
# Educational information
|
| 589 |
+
with st.expander(f"ℹ️ About {integral_name} Integrals"):
|
| 590 |
+
if integral_name == "Overlap":
|
| 591 |
+
st.markdown("""
|
| 592 |
+
**Overlap Integrals (S)**
|
| 593 |
+
|
| 594 |
+
The overlap integral measures how much two basis functions overlap in space:
|
| 595 |
+
|
| 596 |
+
$$S_{ij} = \\langle \\phi_i | \\phi_j \\rangle = \\int \\phi_i(\\mathbf{r}) \\phi_j(\\mathbf{r}) d\\mathbf{r}$$
|
| 597 |
+
|
| 598 |
+
- Diagonal elements (Sᵢᵢ) equal 1 for normalized basis functions
|
| 599 |
+
- Off-diagonal elements indicate orbital overlap
|
| 600 |
+
- Essential for orthogonalization and transformation to orthonormal basis
|
| 601 |
+
- Used in Löwdin or canonical orthogonalization schemes
|
| 602 |
+
""")
|
| 603 |
+
elif integral_name == "Kinetic":
|
| 604 |
+
st.markdown("""
|
| 605 |
+
**Kinetic Energy Integrals (T)**
|
| 606 |
+
|
| 607 |
+
Represents the kinetic energy operator in the basis function representation:
|
| 608 |
+
|
| 609 |
+
$$T_{ij} = \\langle \\phi_i | -\\frac{1}{2}\\nabla^2 | \\phi_j \\rangle$$
|
| 610 |
+
|
| 611 |
+
- Contains the electronic kinetic energy contribution
|
| 612 |
+
- Always positive (kinetic energy is positive definite)
|
| 613 |
+
- Diagonal elements are largest (self-kinetic energy)
|
| 614 |
+
- Critical for total electronic energy calculations
|
| 615 |
+
""")
|
| 616 |
+
elif integral_name == "Nuclear":
|
| 617 |
+
st.markdown("""
|
| 618 |
+
**Nuclear Attraction Integrals (V)**
|
| 619 |
+
|
| 620 |
+
Represents electron-nuclear attraction energy:
|
| 621 |
+
|
| 622 |
+
$$V_{ij} = \\langle \\phi_i | -\\sum_A \\frac{Z_A}{r_A} | \\phi_j \\rangle$$
|
| 623 |
+
|
| 624 |
+
- Sum over all nuclei A with charge Zₐ
|
| 625 |
+
- Always negative (attractive interaction)
|
| 626 |
+
- Largest near nuclear positions
|
| 627 |
+
- Molecular geometry dependent
|
| 628 |
+
""")
|
| 629 |
+
elif integral_name == "4c2e":
|
| 630 |
+
st.markdown("""
|
| 631 |
+
**Four-Center Two-Electron Repulsion Integrals (ERI)**
|
| 632 |
+
|
| 633 |
+
The most computationally expensive integrals in quantum chemistry:
|
| 634 |
+
|
| 635 |
+
$$ERI_{ijkl} = \\langle \\phi_i \\phi_j | \\frac{1}{r_{12}} | \\phi_k \\phi_l \\rangle$$
|
| 636 |
+
|
| 637 |
+
- Four-index tensor: scales as O(N⁴) with basis size
|
| 638 |
+
- Symmetries reduce unique elements: (ij|kl) = (ji|kl) = (ij|lk) = (kl|ij)
|
| 639 |
+
- Used for electron-electron repulsion (Coulomb and exchange)
|
| 640 |
+
""")
|
| 641 |
+
elif integral_name == "3c2e":
|
| 642 |
+
st.markdown("""
|
| 643 |
+
**Three-Center Two-Electron Integrals**
|
| 644 |
+
|
| 645 |
+
Used in density fitting (RI) approximations:
|
| 646 |
+
|
| 647 |
+
$$(\\phi_i \\phi_j | P)$$
|
| 648 |
+
|
| 649 |
+
- Three indices instead of four
|
| 650 |
+
- Auxiliary basis function P
|
| 651 |
+
- Enables efficient approximation of 4c2e integrals
|
| 652 |
+
- Critical for linear-scaling DFT methods
|
| 653 |
+
""")
|
| 654 |
+
elif integral_name == "2c2e":
|
| 655 |
+
st.markdown("""
|
| 656 |
+
**Two-Center Two-Electron Integrals**
|
| 657 |
+
|
| 658 |
+
Coulomb metric in auxiliary basis:
|
| 659 |
+
|
| 660 |
+
$$(P | Q)$$
|
| 661 |
+
|
| 662 |
+
- Two-index matrix
|
| 663 |
+
- Auxiliary basis only
|
| 664 |
+
- Used in density fitting inverse metric
|
| 665 |
+
- Much smaller than full ERI tensor
|
| 666 |
+
""")
|
| 667 |
+
|
| 668 |
+
# Matrix visualization
|
| 669 |
+
col_a, col_b = st.columns([1.5, 1])
|
| 670 |
+
|
| 671 |
+
with col_a:
|
| 672 |
+
st.markdown("**Matrix Visualization**")
|
| 673 |
+
|
| 674 |
+
# Handle different dimensionalities
|
| 675 |
+
if len(integral_matrix.shape) == 2:
|
| 676 |
+
# 2D matrix (one-electron integrals or 2c2e)
|
| 677 |
+
fig = px.imshow(integral_matrix,
|
| 678 |
+
color_continuous_scale='RdBu_r',
|
| 679 |
+
aspect='auto',
|
| 680 |
+
labels={'x': 'Basis Function j', 'y': 'Basis Function i', 'color': 'Value'})
|
| 681 |
+
fig.update_layout(height=400, title=f"{integral_name} Matrix Heatmap")
|
| 682 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 683 |
+
|
| 684 |
+
elif len(integral_matrix.shape) == 3:
|
| 685 |
+
# 3D tensor (3c2e)
|
| 686 |
+
st.info("3D tensor - showing slice along first auxiliary basis index")
|
| 687 |
+
slice_idx = st.slider(f"Auxiliary basis index:", 0, integral_matrix.shape[0]-1, 0)
|
| 688 |
+
fig = px.imshow(integral_matrix[slice_idx],
|
| 689 |
+
color_continuous_scale='RdBu_r',
|
| 690 |
+
aspect='auto',
|
| 691 |
+
labels={'x': 'Basis Function j', 'y': 'Basis Function i', 'color': 'Value'})
|
| 692 |
+
fig.update_layout(height=400, title=f"{integral_name} Matrix (Slice {slice_idx})")
|
| 693 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 694 |
+
|
| 695 |
+
elif len(integral_matrix.shape) == 4:
|
| 696 |
+
# 4D tensor (4c2e)
|
| 697 |
+
st.info("4D tensor - showing 2D slice")
|
| 698 |
+
col_s1, col_s2 = st.columns(2)
|
| 699 |
+
with col_s1:
|
| 700 |
+
k_idx = st.slider("Index k:", 0, integral_matrix.shape[2]-1, 0, key='k_idx')
|
| 701 |
+
with col_s2:
|
| 702 |
+
l_idx = st.slider("Index l:", 0, integral_matrix.shape[3]-1, 0, key='l_idx')
|
| 703 |
+
|
| 704 |
+
fig = px.imshow(integral_matrix[:, :, k_idx, l_idx],
|
| 705 |
+
color_continuous_scale='RdBu_r',
|
| 706 |
+
aspect='auto',
|
| 707 |
+
labels={'x': 'Basis Function j', 'y': 'Basis Function i', 'color': 'Value'})
|
| 708 |
+
fig.update_layout(height=400, title=f"{integral_name} Matrix (k={k_idx}, l={l_idx})")
|
| 709 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 710 |
+
|
| 711 |
+
with col_b:
|
| 712 |
+
st.markdown("**Matrix Statistics**")
|
| 713 |
+
|
| 714 |
+
# Calculate statistics based on dimensionality
|
| 715 |
+
if len(integral_matrix.shape) == 2:
|
| 716 |
+
mat_stats = {
|
| 717 |
+
"Shape": f"{integral_matrix.shape[0]} × {integral_matrix.shape[1]}",
|
| 718 |
+
"Max Value": f"{np.max(integral_matrix):.6e}",
|
| 719 |
+
"Min Value": f"{np.min(integral_matrix):.6e}",
|
| 720 |
+
"Mean": f"{np.mean(integral_matrix):.6e}",
|
| 721 |
+
"Std Dev": f"{np.std(integral_matrix):.6e}",
|
| 722 |
+
"Frobenius Norm": f"{np.linalg.norm(integral_matrix):.6e}"
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
# Check symmetry for 2D matrices
|
| 726 |
+
if integral_matrix.shape[0] == integral_matrix.shape[1]:
|
| 727 |
+
symmetry_error = np.max(np.abs(integral_matrix - integral_matrix.T))
|
| 728 |
+
mat_stats["Symmetry Error"] = f"{symmetry_error:.6e}"
|
| 729 |
+
|
| 730 |
+
# Check if matrix is positive definite (for overlap)
|
| 731 |
+
if integral_name == "Overlap":
|
| 732 |
+
eigenvalues = np.linalg.eigvalsh(integral_matrix)
|
| 733 |
+
mat_stats["Min Eigenvalue"] = f"{np.min(eigenvalues):.6e}"
|
| 734 |
+
mat_stats["Condition Number"] = f"{np.max(eigenvalues)/np.min(eigenvalues):.2e}"
|
| 735 |
+
|
| 736 |
+
elif len(integral_matrix.shape) == 3:
|
| 737 |
+
mat_stats = {
|
| 738 |
+
"Shape": f"{integral_matrix.shape[0]} × {integral_matrix.shape[1]} × {integral_matrix.shape[2]}",
|
| 739 |
+
"Total Elements": f"{integral_matrix.size:,}",
|
| 740 |
+
"Max Value": f"{np.max(integral_matrix):.6e}",
|
| 741 |
+
"Min Value": f"{np.min(integral_matrix):.6e}",
|
| 742 |
+
"Mean": f"{np.mean(integral_matrix):.6e}",
|
| 743 |
+
"Memory": f"{integral_matrix.nbytes / 1024:.2f} KB"
|
| 744 |
+
}
|
| 745 |
+
|
| 746 |
+
elif len(integral_matrix.shape) == 4:
|
| 747 |
+
mat_stats = {
|
| 748 |
+
"Shape": f"{integral_matrix.shape[0]} × {integral_matrix.shape[1]} × {integral_matrix.shape[2]} × {integral_matrix.shape[3]}",
|
| 749 |
+
"Total Elements": f"{integral_matrix.size:,}",
|
| 750 |
+
"Max Value": f"{np.max(integral_matrix):.6e}",
|
| 751 |
+
"Min Value": f"{np.min(integral_matrix):.6e}",
|
| 752 |
+
"Mean": f"{np.mean(integral_matrix):.6e}",
|
| 753 |
+
"Memory": f"{integral_matrix.nbytes / (1024*1024):.2f} MB"
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
# Note about 8-fold symmetry
|
| 757 |
+
st.info("**Symmetry**: ERI tensor has 8-fold permutational symmetry")
|
| 758 |
+
|
| 759 |
+
for key, value in mat_stats.items():
|
| 760 |
+
st.write(f"**{key}:** {value}")
|
| 761 |
+
|
| 762 |
+
# Matrix data table (expandable)
|
| 763 |
+
with st.expander(f"📊 View {integral_name} Matrix Data"):
|
| 764 |
+
if len(integral_matrix.shape) == 2:
|
| 765 |
+
df = pd.DataFrame(integral_matrix)
|
| 766 |
+
df.columns = [f"j={i}" for i in range(df.shape[1])]
|
| 767 |
+
df.index = [f"i={i}" for i in range(df.shape[0])]
|
| 768 |
+
st.dataframe(df, use_container_width=True, height=400)
|
| 769 |
+
else:
|
| 770 |
+
st.write(f"Shape: {integral_matrix.shape}")
|
| 771 |
+
st.write("Full tensor too large to display as table. Use visualization above.")
|
| 772 |
+
|
| 773 |
+
# Option to view specific elements
|
| 774 |
+
st.markdown("**Query Specific Element:**")
|
| 775 |
+
if len(integral_matrix.shape) == 3:
|
| 776 |
+
col_q1, col_q2, col_q3 = st.columns(3)
|
| 777 |
+
with col_q1:
|
| 778 |
+
q_i = st.number_input("Index i:", 0, integral_matrix.shape[0]-1, 0, key=f"q_i_{integral_name}")
|
| 779 |
+
with col_q2:
|
| 780 |
+
q_j = st.number_input("Index j:", 0, integral_matrix.shape[1]-1, 0, key=f"q_j_{integral_name}")
|
| 781 |
+
with col_q3:
|
| 782 |
+
q_k = st.number_input("Index k:", 0, integral_matrix.shape[2]-1, 0, key=f"q_k_{integral_name}")
|
| 783 |
+
st.write(f"**Value [{q_i},{q_j},{q_k}]:** {integral_matrix[q_i, q_j, q_k]:.8e}")
|
| 784 |
+
|
| 785 |
+
elif len(integral_matrix.shape) == 4:
|
| 786 |
+
col_q1, col_q2, col_q3, col_q4 = st.columns(4)
|
| 787 |
+
with col_q1:
|
| 788 |
+
q_i = st.number_input("Index i:", 0, integral_matrix.shape[0]-1, 0, key=f"q_i_{integral_name}")
|
| 789 |
+
with col_q2:
|
| 790 |
+
q_j = st.number_input("Index j:", 0, integral_matrix.shape[1]-1, 0, key=f"q_j_{integral_name}")
|
| 791 |
+
with col_q3:
|
| 792 |
+
q_k = st.number_input("Index k:", 0, integral_matrix.shape[2]-1, 0, key=f"q_k_{integral_name}")
|
| 793 |
+
with col_q4:
|
| 794 |
+
q_l = st.number_input("Index l:", 0, integral_matrix.shape[3]-1, 0, key=f"q_l_{integral_name}")
|
| 795 |
+
st.write(f"**Value [{q_i},{q_j},{q_k},{q_l}]:** {integral_matrix[q_i, q_j, q_k, q_l]:.8e}")
|
| 796 |
+
|
| 797 |
+
|
| 798 |
+
st.markdown("---")
|
| 799 |
+
|
| 800 |
+
# Educational insights section
|
| 801 |
+
if calc_overlap and calc_kinetic and calc_nuclear:
|
| 802 |
+
st.header("4. Educational Insights")
|
| 803 |
+
|
| 804 |
+
st.subheader("Core Hamiltonian Matrix (H_core)")
|
| 805 |
+
st.markdown("""
|
| 806 |
+
The core Hamiltonian combines kinetic and nuclear attraction integrals:
|
| 807 |
+
|
| 808 |
+
$H^{\\text{core}}_{ij} = T_{ij} + V_{ij}$
|
| 809 |
+
|
| 810 |
+
This represents the one-electron part of the Hamiltonian in the Hartree-Fock and DFT methods.
|
| 811 |
+
""")
|
| 812 |
+
|
| 813 |
+
H_core = results['Kinetic'] + results['Nuclear']
|
| 814 |
+
|
| 815 |
+
col_h1, col_h2 = st.columns([1.5, 1])
|
| 816 |
+
|
| 817 |
+
with col_h1:
|
| 818 |
+
fig = px.imshow(H_core,
|
| 819 |
+
color_continuous_scale='RdBu_r',
|
| 820 |
+
aspect='auto',
|
| 821 |
+
labels={'x': 'Basis Function j', 'y': 'Basis Function i', 'color': 'Energy (Ha)'})
|
| 822 |
+
fig.update_layout(height=400, title="Core Hamiltonian Matrix")
|
| 823 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 824 |
+
|
| 825 |
+
with col_h2:
|
| 826 |
+
st.markdown("**Properties:**")
|
| 827 |
+
eigenvalues = np.linalg.eigvalsh(H_core)
|
| 828 |
+
st.write(f"**Lowest eigenvalue:** {np.min(eigenvalues):.6f} Ha")
|
| 829 |
+
st.write(f"**Highest eigenvalue:** {np.max(eigenvalues):.6f} Ha")
|
| 830 |
+
st.write(f"**Energy range:** {np.max(eigenvalues) - np.min(eigenvalues):.6f} Ha")
|
| 831 |
+
|
| 832 |
+
# Eigenvalue distribution
|
| 833 |
+
fig_eig = go.Figure()
|
| 834 |
+
fig_eig.add_trace(go.Scatter(
|
| 835 |
+
x=list(range(len(eigenvalues))),
|
| 836 |
+
y=eigenvalues * 27.2114, # Convert to eV
|
| 837 |
+
mode='markers+lines',
|
| 838 |
+
name='Eigenvalues'
|
| 839 |
+
))
|
| 840 |
+
fig_eig.update_layout(
|
| 841 |
+
title="Core Hamiltonian Eigenvalues",
|
| 842 |
+
xaxis_title="Index",
|
| 843 |
+
yaxis_title="Energy (eV)",
|
| 844 |
+
height=300
|
| 845 |
+
)
|
| 846 |
+
st.plotly_chart(fig_eig, use_container_width=True)
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
# Initial instructions
|
| 852 |
+
st.info("👆 Configure your molecule and select integrals to calculate above!")
|
| 853 |
+
|
| 854 |
+
st.markdown("""
|
| 855 |
+
### About Molecular Integrals
|
| 856 |
+
|
| 857 |
+
Molecular integrals are the fundamental building blocks of quantum chemistry calculations. This tool allows you to:
|
| 858 |
+
|
| 859 |
+
1. **Calculate different types of integrals** - from simple overlap to complex four-center electron repulsion integrals
|
| 860 |
+
2. **Visualize integral matrices** - see the structure and patterns in the data
|
| 861 |
+
3. **Learn quantum chemistry** - educational explanations for each integral type
|
| 862 |
+
4. **Export results** - download matrices for further analysis
|
| 863 |
+
|
| 864 |
+
### Types of Integrals
|
| 865 |
+
|
| 866 |
+
**One-Electron Integrals:**
|
| 867 |
+
- **Overlap (S)**: Measures basis function overlap
|
| 868 |
+
- **Kinetic (T)**: Electronic kinetic energy
|
| 869 |
+
- **Nuclear (V)**: Electron-nuclear attraction
|
| 870 |
+
|
| 871 |
+
**Two-Electron Integrals:**
|
| 872 |
+
- **4c2e (ERI)**: Four-center electron repulsion - the most computationally intensive
|
| 873 |
+
- **3c2e**: Three-center integrals for density fitting
|
| 874 |
+
- **2c2e**: Two-center auxiliary basis integrals
|
| 875 |
+
|
| 876 |
+
### Performance Tips
|
| 877 |
+
|
| 878 |
+
- Start with small molecules and minimal basis sets (sto-3g)
|
| 879 |
+
- 4c2e integrals scale as O(N⁴) - they become very large quickly
|
| 880 |
+
- Consider using subsets for exploration of large systems
|
| 881 |
+
|
| 882 |
+
### Educational Use
|
| 883 |
+
|
| 884 |
+
This tool is perfect for:
|
| 885 |
+
- Learning how basis functions interact
|
| 886 |
+
- Understanding the structure of integral matrices
|
| 887 |
+
- Exploring symmetries in quantum chemistry
|
| 888 |
+
- Teaching computational chemistry concepts
|
| 889 |
+
- Comparing different computational methods
|
| 890 |
+
""")
|
| 891 |
+
|
| 892 |
+
# Footer
|
| 893 |
+
st.markdown("---")
|
| 894 |
+
st.markdown("""
|
| 895 |
+
<div style='text-align: center'>
|
| 896 |
+
<p>PyFock Molecular Integrals Calculator</p>
|
| 897 |
+
<p>⚡ Fast • 🎯 Accurate • 🐍 Pure Python</p>
|
| 898 |
+
</div>
|
| 899 |
+
""", unsafe_allow_html=True)
|