ManasSharma07 commited on
Commit
bd7fc15
·
verified ·
1 Parent(s): 6454a02

Create Home.py

Browse files
Files changed (1) hide show
  1. src/Home.py +1025 -0
src/Home.py ADDED
@@ -0,0 +1,1025 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 pyscf import gto, dft, scf
10
+ from pyscf.dft import gen_grid
11
+ import re
12
+ import base64
13
+ import contextlib
14
+ import sys
15
+ import io as _io
16
+ from ase import Atoms
17
+ from ase.io import read as ase_read
18
+ import pandas as pd
19
+
20
+ # Set page configuration
21
+ st.set_page_config(
22
+ page_title='PyFock GUI - Interactive DFT Calculations',
23
+ layout='wide',
24
+ page_icon="⚛️",
25
+ menu_items={
26
+ 'About': "PyFock GUI - A web interface for PyFock, a pure Python DFT code with Numba JIT acceleration"
27
+ }
28
+ )
29
+
30
+ # Sidebar with enhanced styling
31
+ st.sidebar.image("https://raw.githubusercontent.com/manassharma07/PyFock/main/logo_crysx_pyfock.png", use_container_width=True)
32
+
33
+ st.sidebar.markdown("---")
34
+
35
+ # About PyFock section
36
+ st.sidebar.markdown("### About PyFock")
37
+ st.sidebar.markdown("""
38
+ **Pure Python DFT** with performance matching C++ codes!
39
+
40
+ **Key Advantages:**
41
+ - 100% Pure Python (including molecular integrals)
42
+ - Numba JIT acceleration
43
+ - GPU support (CUDA via CuPy)
44
+ - Near-quadratic scaling (~O(N²·⁰⁵))
45
+ - Accuracy matching PySCF (<10⁻⁷ Ha)
46
+ - Windows/Linux/MacOS compatible
47
+ - Easy pip installation
48
+ """)
49
+
50
+ st.sidebar.markdown("---")
51
+
52
+ # Features
53
+ st.sidebar.markdown("### GUI Features")
54
+ st.sidebar.markdown("""
55
+ * Run DFT in your browser
56
+ * Visualize HOMO, LUMO, density
57
+ * Compare with PySCF
58
+ * Download cube files & scripts
59
+ * Interactive 3D visualization
60
+ * No installation required!
61
+ """)
62
+
63
+ st.sidebar.markdown("---")
64
+
65
+ # Links section
66
+ st.sidebar.markdown("### 🔗 Links & Resources")
67
+ st.sidebar.markdown("""
68
+ [![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?logo=github)](https://github.com/manassharma07/PyFock)
69
+ [![PyPI](https://img.shields.io/badge/PyPI-Package-orange?logo=pypi)](https://pypi.org/project/pyfock/)
70
+ [![Docs](https://img.shields.io/badge/Documentation-Read-green?logo=readthedocs)](https://github.com/manassharma07/PyFock/blob/main/Documentation.md)
71
+
72
+ 📄 **Paper:** [arXiv preprint](https://arxiv.org) *(coming soon)*
73
+
74
+ 👨‍💻 **Developer:** [Manas Sharma](https://www.linkedin.com/in/manassharma07)
75
+
76
+ ⭐ **Star the repo** if you find it useful!
77
+ """)
78
+
79
+ st.sidebar.markdown("---")
80
+
81
+ # Installation
82
+ with st.sidebar.expander("📦 Installation Instructions"):
83
+ st.code("""
84
+ # Install PyFock
85
+ pip install pyfock
86
+
87
+ # For GPU support
88
+ pip install cupy-cuda12x # or appropriate version
89
+
90
+ # Install dependencies
91
+ pip install pyscf numba numpy scipy
92
+ """, language="bash")
93
+
94
+ st.sidebar.markdown("---")
95
+
96
+ # Performance highlights
97
+ with st.sidebar.expander("⚡ Performance Highlights"):
98
+ st.markdown("""
99
+ **CPU Performance:**
100
+ - Upto 2x faster than PySCF
101
+ - Strong scaling up to 32 cores
102
+ - ~O(N²·⁰⁵) scaling with basis functions
103
+
104
+ **GPU Acceleration:**
105
+ - Up to **14× speedup** vs 4-core CPU
106
+ - Single A100 GPU handles 4000+ basis functions
107
+ - Consumer GPUs (RTX series) supported
108
+ """)
109
+
110
+ st.sidebar.markdown("---")
111
+ st.sidebar.markdown("*Made with PyFock by PhysWhiz*")
112
+ st.sidebar.markdown("*Pure Python • Numba JIT • GPU Ready*")
113
+
114
+ def strip_ansi(text):
115
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
116
+ return ansi_escape.sub('', text)
117
+
118
+ # Helper: create an ASE Atoms object or fallback
119
+ def _parse_xyz_to_atoms(xyz_text):
120
+ # ASE can read from string via io
121
+ return ase_read(StringIO(xyz_text), format='xyz')
122
+
123
+ # get_structure_viz2 implementation (based on sample provided)
124
+ def get_structure_viz2(atoms_obj, style='stick', width=400, height=400):
125
+ xyz_str = ""
126
+ xyz_str += f"{len(atoms_obj)}\n"
127
+ xyz_str += "Structure\n"
128
+ for atom in atoms_obj:
129
+ # atom may be ASE Atoms or fallback MiniAtom
130
+ sym = atom.symbol if hasattr(atom, 'symbol') else atom.get_chemical_symbols()[0]
131
+ pos = atom.position if hasattr(atom, 'position') else atom.position
132
+ xyz_str += f"{sym} {pos[0]:.6f} {pos[1]:.6f} {pos[2]:.6f}\n"
133
+ view = py3Dmol.view(width=width, height=height)
134
+ view.addModel(xyz_str, "xyz")
135
+ if style.lower() == 'ball-stick':
136
+ view.setStyle({'stick': {'radius': 0.2}, 'sphere': {'scale': 0.3}})
137
+ elif style.lower() == 'stick':
138
+ view.setStyle({'stick': {}})
139
+ elif style.lower() == 'ball':
140
+ view.setStyle({'sphere': {'scale': 0.4}})
141
+ else:
142
+ view.setStyle({'stick': {'radius': 0.15}})
143
+ try:
144
+ pbc_any = atoms_obj.pbc.any()
145
+ except Exception:
146
+ pbc_any = False
147
+
148
+ view.zoomTo()
149
+ view.setBackgroundColor('white')
150
+ return view
151
+
152
+ # === Cube visualization function ===
153
+ def visualize_cube_in_component(cube_content, title, iso_val, opac, width=420, height=360):
154
+ # return the HTML for embedding so it can be used inside columns
155
+ view = py3Dmol.view(width=width, height=height)
156
+ view.addModel(cube_content, 'cube')
157
+ view.setStyle({'sphere': {'colorscheme': 'Jmol', 'scale': 0.3},
158
+ 'stick': {'colorscheme': 'Jmol', 'radius': 0.2}})
159
+
160
+ # For orbitals, show both lobes; for density, show only positive + negative appropriately
161
+ if 'Density' not in title:
162
+ view.addVolumetricData(cube_content, 'cube',
163
+ {'isoval': -abs(iso_val), 'color': 'blue', 'opacity': opac})
164
+ view.addVolumetricData(cube_content, 'cube',
165
+ {'isoval': abs(iso_val), 'color': 'red', 'opacity': opac})
166
+
167
+ view.zoomTo()
168
+ view.setClickable({'clickable': 'true'})
169
+ view.enableContextMenu({'contextMenuEnabled': 'true'})
170
+ view.show()
171
+ view.render()
172
+ # don't spin automatically; allow user to toggle via JS later if desired
173
+ t = view.js()
174
+ html_content = f"{t.startjs}{t.endjs}"
175
+ return html_content
176
+
177
+ # Example XYZ files
178
+ EXAMPLE_MOLECULES = {
179
+ "Water": """3
180
+ Water molecule
181
+ O 0.000000 0.000000 0.117790
182
+ H 0.000000 0.755453 -0.471161
183
+ H 0.000000 -0.755453 -0.471161""",
184
+
185
+ "Acetone": """10
186
+ Acetone molecule
187
+ O 0.000247197289657 -1.311344859924947 0.000033372829371
188
+ C 0.000008761532627 -0.103796835732344 0.000232428229233
189
+ C 1.285011287026515 0.689481114475586 -0.000005118102586
190
+ C -1.285310305026895 0.688972899031773 -0.000008308924344
191
+ H 1.326033303131908 1.335179621002258 -0.879574382138206
192
+ H 1.324106820690737 1.339904097018082 0.876116213728557
193
+ H 2.136706543431990 0.014597477886500 0.002551051783708
194
+ H -2.136748467815155 0.013761611436540 0.002550873429523
195
+ H -1.326572603227431 1.334639056170679 -0.879590243114624
196
+ H -1.324682534264540 1.339405821389821 0.876094109485741""",
197
+
198
+ "Acetonitrile": """6
199
+ Acetonitrile molecule
200
+ N 1.238504335855378 -0.000002648443155 -0.000006432209062
201
+ C -1.367114484335133 0.000020833177302 0.000019691866100
202
+ C 0.091462639284387 0.000004939432085 -0.000014578846105
203
+ H -1.737603541725548 -0.830506551932429 0.597694569897051
204
+ H -1.737658959168382 -0.102297452169442 -1.018057029309221
205
+ H -1.737589980824850 0.932880879510178 0.420463777423243""",
206
+
207
+ "Methane": """5
208
+ Methane molecule
209
+ C 0.000000 0.000000 0.000000
210
+ H 0.629118 0.629118 0.629118
211
+ H -0.629118 -0.629118 0.629118
212
+ H -0.629118 0.629118 -0.629118
213
+ H 0.629118 -0.629118 -0.629118""",
214
+
215
+ "Benzene": """12
216
+ Benzene molecule
217
+ C 1.395890 0.000000 0.000000
218
+ C 0.697945 1.209021 0.000000
219
+ C -0.697945 1.209021 0.000000
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
+ H 2.482610 0.000000 0.000000
224
+ H 1.241305 2.149540 0.000000
225
+ H -1.241305 2.149540 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
+
230
+ "Ammonia": """4
231
+ Ammonia molecule
232
+ N 0.000000 0.000000 0.100000
233
+ H 0.945000 0.000000 -0.266000
234
+ H -0.472500 0.818000 -0.266000
235
+ H -0.472500 -0.818000 -0.266000""",
236
+
237
+ "Carbon Dioxide": """3
238
+ Carbon dioxide molecule
239
+ C 0.000000 0.000000 0.000000
240
+ O 0.000000 0.000000 1.160000
241
+ O 0.000000 0.000000 -1.160000""",
242
+
243
+ "Hydrogen Peroxide": """4
244
+ Hydrogen peroxide molecule
245
+ O 0.000000 0.000000 0.000000
246
+ O 1.450000 0.000000 0.000000
247
+ H 0.000000 0.930000 0.000000
248
+ H 1.450000 -0.930000 0.000000""",
249
+
250
+ "Formaldehyde": """4
251
+ Formaldehyde molecule
252
+ C 0.000000 0.000000 0.000000
253
+ O 1.200000 0.000000 0.000000
254
+ H -0.550000 0.940000 0.000000
255
+ H -0.550000 -0.940000 0.000000""",
256
+
257
+ "Hydrogen Cyanide": """3
258
+ Hydrogen cyanide molecule
259
+ H 0.000000 0.000000 0.000000
260
+ C 1.065000 0.000000 0.000000
261
+ N 2.232000 0.000000 0.000000""",
262
+
263
+ "Acetylene": """4
264
+ Acetylene molecule
265
+ H 0.000000 0.000000 0.000000
266
+ C 0.601000 0.000000 0.000000
267
+ C 1.764000 0.000000 0.000000
268
+ H 2.365000 0.000000 0.000000""",
269
+
270
+ "Ethylene": """6
271
+ Ethylene molecule
272
+ C 0.000000 0.000000 0.000000
273
+ C 1.339000 0.000000 0.000000
274
+ H -0.540000 0.930000 0.000000
275
+ H -0.540000 -0.930000 0.000000
276
+ H 1.879000 0.930000 0.000000
277
+ H 1.879000 -0.930000 0.000000""",
278
+
279
+ "Ethane": """8
280
+ Ethane molecule
281
+ C 0.000000 0.000000 0.000000
282
+ C 1.540000 0.000000 0.000000
283
+ H -0.540000 0.930000 0.000000
284
+ H -0.540000 -0.930000 0.000000
285
+ H 0.000000 0.000000 1.090000
286
+ H 2.080000 0.930000 0.000000
287
+ H 2.080000 -0.930000 0.000000
288
+ H 1.540000 0.000000 -1.090000""",
289
+
290
+ "Formic Acid": """5
291
+ Formic acid molecule
292
+ C 0.000000 0.000000 0.000000
293
+ O 1.200000 0.000000 0.000000
294
+ O -0.600000 1.100000 0.000000
295
+ H 1.700000 0.900000 0.000000
296
+ H -0.600000 -0.900000 0.000000""",
297
+
298
+ "Hydrogen Sulfide": """3
299
+ Hydrogen sulfide molecule
300
+ S 0.000000 0.000000 0.000000
301
+ H 0.960000 0.000000 0.000000
302
+ H -0.480000 0.830000 0.000000""",
303
+
304
+ "Tetrahydrofuran": """13
305
+ Tetrahydrofuran molecule
306
+ O 1.216699382773870 -0.000516422279060 -0.000000629115220
307
+ C -1.016993686921708 -0.728737145702576 -0.227053769599122
308
+ C -1.016379073694511 0.729554513122363 0.227039749920194
309
+ C 0.395888395971846 -1.160306071902647 0.143905791563541
310
+ C 0.396849106350064 1.159943366322044 -0.143953936744947
311
+ H -1.782338327214497 -1.336082444384147 0.254184465508221
312
+ H -1.159922021048819 -0.787714532025205 -1.307880287340100
313
+ H -1.781241112969484 1.337526054547069 -0.254174631443963
314
+ H -1.159223957468540 0.788641683930875 1.307872257115590
315
+ H 0.441717113009142 -1.507461993885669 1.181993316376597
316
+ H 0.789985254249466 -1.947807118924544 -0.499943162908684
317
+ H 0.442962196586526 1.507022288075474 -1.182056728365057
318
+ H 0.791596740442480 1.947137848999537 0.499867568609654""",
319
+
320
+ "Pyrrole": """10
321
+ Pyrrole molecule
322
+ N 0.003181105319591 -1.154989666506124 0.000060405177869
323
+ C -1.117737448486888 -0.370847266235924 -0.000011684111207
324
+ C 1.119766547077291 -0.364686948222411 0.000018496752116
325
+ C -0.713366217362578 0.937235325591941 -0.000096441964893
326
+ C 0.708206650120975 0.941149612298515 -0.000053474487459
327
+ H 0.005944376523274 -2.157707636293536 -0.000153625688957
328
+ H -2.102541509509273 -0.805490708525106 0.000110657494436
329
+ H 2.106949265704444 -0.793899550618045 0.000097097601888
330
+ H -1.362506178052089 1.796728992178547 -0.000156325233850
331
+ H 1.352603398906602 1.804207848609518 -0.000015105544776""",
332
+
333
+ "Dimethyl Ether": """9
334
+ Dimethyl ether molecule
335
+ O 0.000004977280923 0.530020309529034 -0.000005108317779
336
+ C 1.164212882530410 -0.260593898872961 0.000001269096639
337
+ C -1.164190191714442 -0.260612616856000 -0.000020896710708
338
+ H 1.210503469986643 -0.900685141083738 0.889522583088480
339
+ H 2.020207692369726 0.410753932008613 0.000001864695406
340
+ H 1.210505099946668 -0.900685096089216 -0.889519959770612
341
+ H -1.210516092616227 -0.900663538561855 0.889526642712922
342
+ H -1.210427870830911 -0.900750324054947 -0.889509722206098
343
+ H -2.020199970977986 0.410716373006091 -0.000096671211569""",
344
+
345
+ }
346
+
347
+ # XC functional mapping to libxc codes
348
+ XC_FUNCTIONALS = {
349
+ "LDA (SVWN5)": (1, 7), # Slater exchange + VWN5 correlation
350
+ "PBE": (101, 130), # PBE exchange + PBE correlation
351
+ "BLYP": (106, 131), # Becke88 exchange + LYP correlation
352
+ "BP86": (106, 132), # Becke88 exchange + P86 correlation
353
+ }
354
+
355
+ BASIS_SETS = ["sto-3g", "sto-6g", "3-21G", "4-31G", "6-31G", "6-31+G", "6-31++G", "cc-pvDZ", "def2-SVP", "def2-TZVP"]
356
+
357
+ # Main title
358
+ st.title("⚛️ PyFock GUI - Interactive DFT Calculations")
359
+ st.markdown("---")
360
+
361
+ # Input section
362
+ st.header("1. DFT Setup")
363
+
364
+ col1, col2 = st.columns([1.3, 1])
365
+
366
+ with col1:
367
+ st.subheader("Molecule Input")
368
+
369
+ # Molecule selection
370
+ molecule_choice = st.selectbox(
371
+ "Select example molecule or paste custom XYZ:",
372
+ [
373
+ "Water", # correct
374
+ "Acetone", # correct
375
+ "Tetrahydrofuran", # correct
376
+ "Pyrrole", # correct
377
+ "Dimethyl ether", # correct
378
+ # "Acetonitrile",
379
+ # "Methane",
380
+ "Benzene", # correct
381
+ # "Ammonia",
382
+ "Carbon Dioxide", # correct
383
+ "Hydrogen Peroxide",
384
+ # "Formaldehyde",
385
+ # "Hydrogen Cyanide",
386
+ # "Acetylene",
387
+ # "Ethylene",
388
+ # "Ethane",
389
+ "Formic Acid",
390
+ "Hydrogen Sulfide", # correct
391
+ "Custom"
392
+ ]
393
+ )
394
+
395
+ if molecule_choice == "Custom":
396
+ xyz_content = st.text_area(
397
+ "Paste XYZ coordinates:",
398
+ height=200,
399
+ 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"
400
+ )
401
+ else:
402
+ xyz_content = st.text_area(
403
+ "XYZ coordinates:",
404
+ value=EXAMPLE_MOLECULES[molecule_choice],
405
+ height=200
406
+ )
407
+
408
+ # === NEW: Structure visualization right at molecule selection ===
409
+ # This uses ASE if available; otherwise, show a simple py3Dmol view from the XYZ string.
410
+ with col2:
411
+ if xyz_content and xyz_content.strip():
412
+ st.markdown("### Molecule Visualization", unsafe_allow_html=True)
413
+ viz_style = st.selectbox("Select Visualization Style:", ["ball-stick", "stick", "ball"], key="viz_style_select")
414
+ atoms_obj = _parse_xyz_to_atoms(xyz_content)
415
+
416
+ # Render py3Dmol
417
+ view_3d = get_structure_viz2(atoms_obj, style=viz_style, width=400, height=400)
418
+ # Use components.html to insert the viewer HTML
419
+ try:
420
+ st.components.v1.html(view_3d._make_html(), width=420, height=420)
421
+ except Exception:
422
+ # fallback to js html
423
+ t = view_3d.js()
424
+ html_content = f"{t.startjs}{t.endjs}"
425
+ components.html(html_content, height=420, width=420)
426
+
427
+ # Structure information
428
+ st.markdown("### Structure Information")
429
+ atoms_info = {
430
+ "Number of Atoms": len(atoms_obj),
431
+ "Chemical Formula": atoms_obj.get_chemical_formula() if hasattr(atoms_obj, 'get_chemical_formula') else "".join(atoms_obj.get_chemical_symbols()),
432
+ "Atom Types": ", ".join(sorted(list(set(atoms_obj.get_chemical_symbols()))))
433
+ }
434
+
435
+ for key, value in atoms_info.items():
436
+ st.write(f"**{key}:** {value}")
437
+
438
+ with col1:
439
+ st.subheader("Calculation Settings")
440
+
441
+ basis_set = st.selectbox("Basis Set:", BASIS_SETS, index=0)
442
+ auxbasis = st.text_input("Auxiliary Basis:", value="def2-universal-jfit")
443
+
444
+ xc_functional = st.selectbox(
445
+ "XC Functional:",
446
+ list(XC_FUNCTIONALS.keys()),
447
+ index=0
448
+ )
449
+
450
+ max_iterations = st.number_input("Max Iterations:", min_value=1, max_value=16, value=14)
451
+ conv_crit = st.number_input("Convergence Criterion:", min_value=1e-7, max_value=1e-3, value=1e-6, format="%.1e")
452
+ ncores = 1#st.number_input("Number of Cores:", min_value=1, max_value=8, value=4)
453
+ use_pyscf_grids = st.checkbox("Use PySCF Grids", value=True, help="Use either PySCF grids for both PyFock and PySCF DFT calculation or PyFock grids for both PyFock and PySCF DFT calculaiton. Using PySCF grids is recommended as those are more efficient and also makes the comparison with PySCF consistent.")
454
+ compare_pyscf = st.checkbox("Compare with PySCF (may take longer)", value=False, help="Runs a KS-DFT calculation using same settings in PySCF for energy comparison.")
455
+
456
+ st.markdown("---")
457
+
458
+ # Visualization settings
459
+ st.header("2. Cube Generation and Visualization Settings")
460
+ col3, col4, col5 = st.columns(3)
461
+
462
+ with col3:
463
+ cube_resolution = st.slider("Cube File Resolution (nx=ny=nz):", 30, 70, 40)
464
+ with col4:
465
+ isovalue = st.number_input("Isovalue:", 0.0, 1.0, value=0.05, step=0.001, format="%.6f")
466
+ with col5:
467
+ opacity = st.slider("Opacity:", 0.0, 1.0, value=0.90, step=0.01)
468
+
469
+
470
+
471
+ st.markdown("---")
472
+
473
+ # Run calculation button
474
+ if st.button("🚀 Run DFT Calculation", type="primary"):
475
+
476
+ # Validate XYZ input
477
+ if not xyz_content.strip():
478
+ st.error("Please provide XYZ coordinates!")
479
+ st.stop()
480
+
481
+
482
+
483
+ # Progress tracking
484
+ progress_bar = st.progress(0)
485
+ status_text = st.empty()
486
+
487
+ try:
488
+ # Capture stdout/stderr into buffer and show later in the app
489
+ log_buffer = _io.StringIO()
490
+ with contextlib.redirect_stdout(log_buffer), contextlib.redirect_stderr(log_buffer):
491
+ # Setup environment variables
492
+ status_text.text("Setting up environment...")
493
+ progress_bar.progress(5)
494
+
495
+ os.environ['OMP_NUM_THREADS'] = str(ncores)
496
+ os.environ["OPENBLAS_NUM_THREADS"] = str(ncores)
497
+ os.environ["MKL_NUM_THREADS"] = str(ncores)
498
+ os.environ["VECLIB_MAXIMUM_THREADS"] = str(ncores)
499
+ os.environ["NUMEXPR_NUM_THREADS"] = str(ncores)
500
+
501
+ # Import PyFock modules
502
+ status_text.text("Importing PyFock modules...")
503
+ progress_bar.progress(10)
504
+
505
+ from pyfock import Basis, Mol, DFT, Utils, Grids
506
+
507
+ # Create temporary XYZ file
508
+ status_text.text("Creating molecule object...")
509
+ progress_bar.progress(15)
510
+
511
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
512
+ f.write(xyz_content)
513
+ xyz_file = f.name
514
+
515
+ # Initialize molecule
516
+ mol = Mol(coordfile=xyz_file)
517
+
518
+ # Initialize basis sets
519
+ status_text.text(f"Loading basis set: {basis_set}...")
520
+ progress_bar.progress(20)
521
+
522
+ basis = Basis(mol, {'all': Basis.load(mol=mol, basis_name=basis_set)})
523
+ auxbasis_obj = Basis(mol, {'all': Basis.load(mol=mol, basis_name=auxbasis)})
524
+
525
+ # Check actual basis function count
526
+ n_basis = basis.bfs_nao
527
+ if n_basis > 120:
528
+ st.error(f"❌ This system has {n_basis} basis functions, exceeding the limit of 120. Please use a smaller basis set or fewer atoms.")
529
+ os.unlink(xyz_file)
530
+ st.stop()
531
+
532
+ st.info(f"✓ System has {n_basis} basis functions (within limit)")
533
+
534
+ # Get XC functional codes
535
+ funcx, funcc = XC_FUNCTIONALS[xc_functional]
536
+ funcidcrysx = [funcx, funcc]
537
+ funcidpyscf = f"{funcx},{funcc}"
538
+
539
+ # Generating grids
540
+ status_text.text("Generating numerical grids...")
541
+ progress_bar.progress(22)
542
+ if use_pyscf_grids:
543
+ # PySCF grids
544
+ molPySCF = gto.Mole()
545
+ molPySCF.atom = xyz_file
546
+ molPySCF.basis = basis_set
547
+ molPySCF.cart = True
548
+ molPySCF.verbose = 0
549
+ molPySCF.build()
550
+ grids = gen_grid.Grids(molPySCF)
551
+ grids.level = 3 # optional: quality of grid (0–9 approx)
552
+ grids.prune = None # disable pruning if you want the full Lebedev mesh
553
+ grids.build(with_non0tab=True)
554
+ else:
555
+ # PyFock grids
556
+ grids = Grids(mol, basis=basis, level = 3, radial_precision=1.0e-13, ncores=ncores)
557
+
558
+
559
+
560
+ # Initialize DFT object
561
+ status_text.text("Initializing DFT calculation...")
562
+ progress_bar.progress(25)
563
+
564
+ dftObj = DFT(mol, basis, auxbasis_obj, xc=funcidcrysx, grids=grids, gridsLevel=3)
565
+ dftObj.conv_crit = conv_crit
566
+ dftObj.max_itr = max_iterations
567
+ dftObj.ncores = 1
568
+ dftObj.save_ao_values = True
569
+
570
+ # Run SCF calculation
571
+ status_text.text("Running SCF calculation... This may take a few moments.")
572
+ progress_bar.progress(30)
573
+
574
+ start_time = time.time()
575
+ energyPyFock, dmat = dftObj.scf()
576
+ pyfock_time = time.time() - start_time
577
+
578
+ progress_bar.progress(50)
579
+
580
+ # Display results
581
+ st.success(f"✅ PyFock calculation completed in {pyfock_time:.2f} seconds!")
582
+
583
+ st.header("3. Results")
584
+
585
+ # Energy and basic properties
586
+ col6, col7, col8 = st.columns(3)
587
+
588
+ with col6:
589
+ st.metric("Total Energy (PyFock)", f"{energyPyFock:.8f} Ha")
590
+
591
+ with st.expander("Energy Components"):
592
+ import pandas as pd
593
+ energy_df = pd.DataFrame({
594
+ "Component": [
595
+ "Kinetic Energy",
596
+ "Nuclear-Electron Attraction",
597
+ "Electron-Electron Repulsion",
598
+ "Exchange-Correlation",
599
+ "Nuclear Repulsion"
600
+ ],
601
+ "Energy (Ha)": [
602
+ f"{dftObj.Kinetic_energy:.8f}",
603
+ f"{dftObj.Nuc_energy:.8f}",
604
+ f"{dftObj.J_energy:.8f}",
605
+ f"{dftObj.XC_energy:.8f}",
606
+ f"{dftObj.Nuclear_repulsion_energy:.8f}"
607
+ ]
608
+ })
609
+ st.dataframe(energy_df, hide_index=True, use_container_width=True)
610
+
611
+ with col7:
612
+ # Calculate HOMO-LUMO gap
613
+ occupied = np.where(dftObj.mo_occupations > 1e-8)[0]
614
+ if len(occupied) > 0 and len(occupied) < len(dftObj.mo_energies):
615
+ homo_idx = occupied[-1]
616
+ lumo_idx = homo_idx + 1
617
+ homo_energy = dftObj.mo_energies[homo_idx]
618
+ lumo_energy = dftObj.mo_energies[lumo_idx]
619
+ gap = (lumo_energy - homo_energy) * 27.2114 # Convert to eV
620
+ st.metric("HOMO-LUMO Gap", f"{gap:.4f} eV")
621
+ else:
622
+ homo_idx = None
623
+ lumo_idx = None
624
+ st.metric("HOMO-LUMO Gap", "N/A")
625
+
626
+ with col8:
627
+ # st.metric("SCF Iterations", f"{len(dftObj.energy_list)}")
628
+ st.write('TODO: SCF Iterations')
629
+
630
+ # MO energies
631
+ st.subheader("Molecular Orbital Energies")
632
+ mo_energies_ev = dftObj.mo_energies * 27.2114 # Convert to eV
633
+
634
+ col9, col10 = st.columns(2)
635
+ with col9:
636
+ if homo_idx is not None:
637
+ st.write(f"**HOMO (orbital {homo_idx}):** {mo_energies_ev[homo_idx]:.4f} eV")
638
+ with col10:
639
+ if lumo_idx is not None:
640
+ st.write(f"**LUMO (orbital {lumo_idx}):** {mo_energies_ev[lumo_idx]:.4f} eV")
641
+
642
+ # Show MO energies
643
+ with st.expander("View All MO Energies"):
644
+ mo_data = {
645
+ "Orbital": list(range(len(mo_energies_ev))),
646
+ "Energy (eV)": [f"{e:.6f}" for e in mo_energies_ev],
647
+ "Occupation": dftObj.mo_occupations
648
+ }
649
+ st.dataframe(mo_data, height=300)
650
+ # Density matrix expander and download ===
651
+ with st.expander("Density Matrix (dmat) — view / download"):
652
+ try:
653
+ st.write(dmat)
654
+
655
+ except Exception as e:
656
+ st.write("Failed to show density matrix:", str(e))
657
+
658
+ # Generate cube files
659
+ status_text.text("Generating cube files for visualization...")
660
+ progress_bar.progress(60)
661
+
662
+ cube_files = {}
663
+
664
+ if homo_idx is not None:
665
+ # HOMO cube
666
+ status_text.text("Generating HOMO cube file...")
667
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_HOMO.cube', delete=False) as f:
668
+ homo_cube_file = f.name
669
+
670
+ Utils.write_orbital_cube(
671
+ mol, basis, dftObj.mo_coefficients[:, homo_idx],
672
+ homo_cube_file, nx=cube_resolution, ny=cube_resolution, nz=cube_resolution,
673
+ ncores=ncores
674
+ )
675
+
676
+ with open(homo_cube_file, 'r') as f:
677
+ cube_files['HOMO'] = f.read()
678
+
679
+ progress_bar.progress(70)
680
+
681
+ if lumo_idx is not None:
682
+ # LUMO cube
683
+ status_text.text("Generating LUMO cube file...")
684
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_LUMO.cube', delete=False) as f:
685
+ lumo_cube_file = f.name
686
+
687
+ Utils.write_orbital_cube(
688
+ mol, basis, dftObj.mo_coefficients[:, lumo_idx],
689
+ lumo_cube_file, nx=cube_resolution, ny=cube_resolution, nz=cube_resolution,
690
+ ncores=ncores
691
+ )
692
+
693
+ with open(lumo_cube_file, 'r') as f:
694
+ cube_files['LUMO'] = f.read()
695
+
696
+ progress_bar.progress(80)
697
+
698
+ # Density cube
699
+ status_text.text("Generating electron density cube file...")
700
+ with tempfile.NamedTemporaryFile(mode='w', suffix='_density.cube', delete=False) as f:
701
+ density_cube_file = f.name
702
+
703
+ Utils.write_density_cube(
704
+ mol, basis, dftObj.dmat,
705
+ density_cube_file, nx=cube_resolution, ny=cube_resolution, nz=cube_resolution,
706
+ ncores=ncores
707
+ )
708
+
709
+ with open(density_cube_file, 'r') as f:
710
+ cube_files['Density'] = f.read()
711
+
712
+ progress_bar.progress(85)
713
+
714
+
715
+
716
+ # Display visualizations side-by-side
717
+ st.subheader("4. Visualizations")
718
+ # create columns for HOMO, LUMO and Density (as available)
719
+ vis_cols = []
720
+ num_vis = len([k for k in cube_files.keys() if cube_files.get(k)])
721
+ if num_vis == 0:
722
+ st.info("No cube visualizations available.")
723
+ else:
724
+ # Arrange into up to three columns side-by-side
725
+ if 'HOMO' in cube_files and 'LUMO' in cube_files and 'Density' in cube_files:
726
+ c1, c2, c3 = st.columns(3)
727
+ vis_cols = [c1, c2, c3]
728
+ mapping = [('HOMO', c1), ('LUMO', c2), ('Density', c3)]
729
+ else:
730
+ # pack present visualizations into equal columns
731
+ keys_present = list(cube_files.keys())
732
+ cols = st.columns(len(keys_present))
733
+ vis_cols = cols
734
+ mapping = list(zip(keys_present, cols))
735
+
736
+ for title, col in mapping:
737
+ if title in cube_files:
738
+ with col:
739
+ st.markdown(f"#### {title}")
740
+ html_blob = visualize_cube_in_component(cube_files[title], title, isovalue, opacity)
741
+ components.html(html_blob, height=380, width=420)
742
+
743
+ col14, col15, col16 = st.columns(3)
744
+
745
+ # Helper to create base64 download links (avoids widget-triggered reruns)
746
+ def make_download_link(content, filename, mimetype="text/plain"):
747
+ if isinstance(content, str):
748
+ b = content.encode()
749
+ else:
750
+ b = content
751
+ b64 = base64.b64encode(b).decode()
752
+ return f'<a href="data:{mimetype};base64,{b64}" download="{filename}">📥 Download {filename}</a>'
753
+
754
+ with col14:
755
+ if 'HOMO' in cube_files:
756
+ st.markdown(make_download_link(cube_files['HOMO'], "homo.cube"), unsafe_allow_html=True)
757
+
758
+ with col15:
759
+ if 'LUMO' in cube_files:
760
+ st.markdown(make_download_link(cube_files['LUMO'], "lumo.cube"), unsafe_allow_html=True)
761
+
762
+ with col16:
763
+ if 'Density' in cube_files:
764
+ st.markdown(make_download_link(cube_files['Density'], "density.cube"), unsafe_allow_html=True)
765
+
766
+ progress_bar.progress(90)
767
+
768
+ # === Allow visualizing any orbital ===
769
+ @st.fragment
770
+ def func_viz_any_mo():
771
+ st.subheader("Visualize any MO")
772
+ mo_idx_choice = None
773
+ if hasattr(dftObj, 'mo_energies'):
774
+ max_orb = len(dftObj.mo_energies) - 1
775
+ # Show a slider/selectbox to pick orbital
776
+ mo_idx_choice = st.number_input("Select orbital index to visualize:", min_value=0, max_value=max_orb, value=homo_idx if homo_idx is not None else 0, step=1)
777
+ # if st.button("Generate and Show Selected MO", key="gen_orb_btn"):
778
+ status_text.text(f"Generating cube for MO index {mo_idx_choice} ...")
779
+ with tempfile.NamedTemporaryFile(mode='w', suffix=f'_MO{mo_idx_choice}.cube', delete=False) as f:
780
+ mo_cube_file = f.name
781
+ Utils.write_orbital_cube(
782
+ mol, basis, dftObj.mo_coefficients[:, int(mo_idx_choice)],
783
+ mo_cube_file, nx=cube_resolution, ny=cube_resolution, nz=cube_resolution,
784
+ ncores=ncores
785
+ )
786
+ with open(mo_cube_file, 'r') as f:
787
+ cube_files[f"MO_{mo_idx_choice}"] = f.read()
788
+ # show it in a small area
789
+ html_blob = visualize_cube_in_component(cube_files[f"MO_{mo_idx_choice}"], f"MO {mo_idx_choice}", isovalue, opacity)
790
+ st.markdown(f"#### MO {mo_idx_choice}")
791
+ components.html(html_blob, height=380, width=420)
792
+
793
+ func_viz_any_mo()
794
+
795
+
796
+ # PySCF comparison
797
+ if compare_pyscf:
798
+ status_text.text("Running PySCF calculation for comparison...")
799
+
800
+ try:
801
+ if not use_pyscf_grids:
802
+ molPySCF = gto.Mole()
803
+ molPySCF.atom = xyz_file
804
+ molPySCF.basis = basis_set
805
+ molPySCF.cart = True
806
+ molPySCF.verbose = 5
807
+ molPySCF.build()
808
+
809
+ mf = dft.rks.RKS(molPySCF).density_fit(auxbasis=auxbasis)
810
+ mf.xc = funcidpyscf
811
+ # mf.direct_scf = False
812
+ mf.conv_tol = conv_crit
813
+ mf.max_cycle = max_iterations
814
+ # mf.grids.level = 5
815
+ mf.grids.coords = grids.coords
816
+ mf.grids.weights = grids.weights
817
+
818
+ # Disable PySCF's automatic grid generation
819
+ mf.grids.build = lambda *args, **kwargs: None
820
+
821
+ start_pyscf = time.time()
822
+ energyPySCF = mf.kernel(dm0 = mf.init_guess_by_1e(molPySCF))
823
+ pyscf_time = time.time() - start_pyscf
824
+
825
+ st.subheader("5. Comparison with PySCF")
826
+
827
+ col11, col12, col13 = st.columns(3)
828
+
829
+ with col11:
830
+ st.metric("PySCF Energy", f"{energyPySCF:.8f} Ha")
831
+
832
+ with col12:
833
+ energy_diff = abs(energyPyFock - energyPySCF) * 1000 # in mHa
834
+ st.metric("Energy Difference", f"{energy_diff:.6f} mHa")
835
+
836
+ with col13:
837
+ speedup = pyscf_time / pyfock_time
838
+ # st.metric("PyFock Speedup", f"{speedup:.2f}x" if speedup > 1 else f"{1/speedup:.2f}x slower")
839
+
840
+ # st.write(f"**PyFock time:** {pyfock_time:.2f} s | **PySCF time:** {pyscf_time:.2f} s")
841
+
842
+ except Exception as e:
843
+ st.warning(f"PySCF comparison failed: {str(e)}")
844
+
845
+ progress_bar.progress(95)
846
+
847
+ # Downloads section
848
+ st.subheader("6. INPUT Script Generation")
849
+
850
+
851
+
852
+
853
+
854
+
855
+
856
+ # Generate Python script
857
+ status_text.text("Generating Python script...")
858
+
859
+ python_script = """# PyFock DFT Calculation Script
860
+ # Generated by PyFock GUI
861
+
862
+ import os
863
+ ncores = {ncores}
864
+ os.environ['OMP_NUM_THREADS'] = str(ncores)
865
+ os.environ["OPENBLAS_NUM_THREADS"] = str(ncores)
866
+ os.environ["MKL_NUM_THREADS"] = str(ncores)
867
+ os.environ["VECLIB_MAXIMUM_THREADS"] = str(ncores)
868
+ os.environ["NUMEXPR_NUM_THREADS"] = str(ncores)
869
+
870
+ from pyfock import Basis, Mol, DFT, Utils
871
+ import numpy as np
872
+
873
+ # XYZ coordinates
874
+ xyz_content = \"\"\"
875
+ {xyz_content}
876
+ \"\"\"
877
+
878
+ # Save XYZ to file
879
+ with open('molecule.xyz', 'w') as f:
880
+ f.write(xyz_content)
881
+
882
+ # Calculation parameters
883
+ basis_set_name = '{basis_set}'
884
+ auxbasis_name = '{auxbasis}'
885
+ funcx = {funcx}
886
+ funcc = {funcc}
887
+ funcidcrysx = [funcx, funcc]
888
+
889
+ # Initialize molecule and basis
890
+ mol = Mol(coordfile='molecule.xyz')
891
+ basis = Basis(mol, {{'all': Basis.load(mol=mol, basis_name=basis_set_name)}})
892
+ auxbasis = Basis(mol, {{'all': Basis.load(mol=mol, basis_name=auxbasis_name)}})
893
+
894
+ # Setup DFT calculation
895
+ dftObj = DFT(mol, basis, auxbasis, xc=funcidcrysx)
896
+ dftObj.conv_crit = {conv_crit}
897
+ dftObj.max_itr = {max_iterations}
898
+ dftObj.ncores = ncores
899
+ dftObj.save_ao_values = True
900
+
901
+ # Run SCF
902
+ energy, dmat = dftObj.scf()
903
+
904
+ print(f"Total Energy: {{energy:.8f}} Ha")
905
+
906
+ # Find HOMO and LUMO
907
+ occupied = np.where(dftObj.mo_occupations > 1e-8)[0]
908
+ if len(occupied) > 0 and len(occupied) < len(dftObj.mo_energies):
909
+ homo_idx = occupied[-1]
910
+ lumo_idx = homo_idx + 1
911
+
912
+ # Generate cube files
913
+ Utils.write_orbital_cube(mol, basis, dftObj.mo_coefficients[:, homo_idx],
914
+ 'HOMO.cube', nx={cube_resolution}, ny={cube_resolution},
915
+ nz={cube_resolution}, ncores=ncores)
916
+
917
+ Utils.write_orbital_cube(mol, basis, dftObj.mo_coefficients[:, lumo_idx],
918
+ 'LUMO.cube', nx={cube_resolution}, ny={cube_resolution},
919
+ nz={cube_resolution}, ncores=ncores)
920
+
921
+ # Generate density cube
922
+ Utils.write_density_cube(mol, basis, dftObj.dmat, 'density.cube',
923
+ nx={cube_resolution}, ny={cube_resolution},
924
+ nz={cube_resolution}, ncores=ncores)
925
+
926
+ print("Cube files generated successfully!")
927
+ """
928
+ parameters = {
929
+ 'ncores': ncores,
930
+ 'xyz_content': xyz_content,
931
+ 'basis_set': basis_set,
932
+ 'auxbasis': auxbasis, # Often a different basis set for auxiliary functions
933
+ 'funcx': funcx, # Example ID for exchange functional (like LDA_X)
934
+ 'funcc': funcc, # Example ID for correlation functional (like LDA_C_PZ)
935
+ 'conv_crit': conv_crit,
936
+ 'max_iterations': max_iterations,
937
+ 'cube_resolution': cube_resolution
938
+ }
939
+ python_script = python_script.format(**parameters)
940
+ with st.expander("Input Script to Run the Above PyFock Calculation"):
941
+ st.code(python_script)
942
+ # Provide script as base64 link (no rerun on click)
943
+ st.markdown(make_download_link(python_script, "pyfock_calculation.py", mimetype="text/x-python"), unsafe_allow_html=True)
944
+
945
+ progress_bar.progress(100)
946
+ status_text.text("✅ All tasks completed!")
947
+
948
+ # === Show captured stdout/stderr logs ===
949
+ st.subheader("7. Calculation Log Output")
950
+ log_buffer.seek(0)
951
+ log_text = log_buffer.read()
952
+ if log_text.strip():
953
+ st.code(strip_ansi(log_text))
954
+ else:
955
+ st.write("No log output was captured.")
956
+
957
+
958
+ # Cleanup
959
+ os.unlink(xyz_file)
960
+ if 'homo_cube_file' in locals():
961
+ os.unlink(homo_cube_file)
962
+ if 'lumo_cube_file' in locals():
963
+ os.unlink(lumo_cube_file)
964
+ if 'density_cube_file' in locals():
965
+ os.unlink(density_cube_file)
966
+ if 'mo_cube_file' in locals():
967
+ try:
968
+ os.unlink(mo_cube_file)
969
+ except Exception:
970
+ pass
971
+
972
+ except ImportError as e:
973
+ st.error(f"❌ Import Error: {str(e)}")
974
+ st.info("Make sure PyFock is installed: `pip install pyfock`")
975
+ progress_bar.empty()
976
+ status_text.empty()
977
+ except Exception as e:
978
+ st.error(f"❌ Calculation failed: {str(e)}")
979
+ st.info("Please check your input parameters and try again.")
980
+ progress_bar.empty()
981
+ status_text.empty()
982
+
983
+ # Cleanup on error
984
+ if 'xyz_file' in locals():
985
+ try:
986
+ os.unlink(xyz_file)
987
+ except:
988
+ pass
989
+
990
+ else:
991
+ # Initial instructions
992
+ st.info("👆 Configure your calculation parameters above and click 'Run DFT Calculation' to start!")
993
+
994
+ st.markdown("""
995
+ ### Getting Started
996
+
997
+ 1. **Choose a molecule**: Select from examples or paste your own XYZ coordinates
998
+ 2. **Select calculation parameters**: Basis set, functional, convergence criteria
999
+ 3. **Adjust visualization settings**: Cube resolution, isovalue, opacity
1000
+ 4. **Run calculation**: Click the button above
1001
+ 5. **Explore results**: View energies, MO properties, and 3D visualizations
1002
+ 6. **Download**: Get cube files and a Python script to reproduce the calculation
1003
+
1004
+ ### Notes
1005
+
1006
+ - Calculations are limited to ~120 basis functions for Streamlit Cloud
1007
+ - Smaller molecules and basis sets will run faster
1008
+ - PySCF comparison adds computation time but validates results
1009
+ - All calculations use density fitting for efficiency
1010
+
1011
+ ### Example Systems
1012
+
1013
+ - **Water**: Quick test system (7 basis functions with sto-3g)
1014
+ - **Methane**: Small hydrocarbon (9 basis functions with sto-3g)
1015
+ - **Benzene**: Aromatic system (42 basis functions with sto-3g)
1016
+ """)
1017
+
1018
+ # Footer
1019
+ st.markdown("---")
1020
+ st.markdown("""
1021
+ <div style='text-align: center'>
1022
+ <p>PyFock GUI - Pure Python DFT with Numba JIT acceleration</p>
1023
+ <p>⚡ Fast • 🎯 Accurate • 🐍 Pure Python</p>
1024
+ </div>
1025
+ """, unsafe_allow_html=True)