ManasSharma07 commited on
Commit
3f7d47f
·
verified ·
1 Parent(s): b9d2657

Upload Molecular_Integrals.py

Browse files
Files changed (1) hide show
  1. 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
+ [![GitHub (PyFock)](https://img.shields.io/badge/GitHub-Repository-blue?logo=github)](https://github.com/manassharma07/PyFock)
103
+ [![GitHub (PyFock GUI)](https://img.shields.io/badge/GitHub-Repository-blue?logo=github)](https://github.com/manassharma07/PyFock-GUI)
104
+ [![PyPI](https://img.shields.io/badge/PyPI-Package-orange?logo=pypi)](https://pypi.org/project/pyfock/)
105
+ [![Docs](https://img.shields.io/badge/Documentation-Read-green?logo=readthedocs)](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)