Vaishnav14220 commited on
Commit
fb7d531
·
1 Parent(s): 1b3c9a7

Simplify to HOMO orbital visualization using Hartree-Fock for faster computation

Browse files
Files changed (1) hide show
  1. app.py +88 -82
app.py CHANGED
@@ -194,114 +194,120 @@ def name_to_3d_molecule(name: str, show_orbitals: bool = False) -> tuple:
194
  if show_orbitals:
195
  try:
196
  import numpy as np
197
- from pyscf import gto, lo, dft
198
 
199
  # Build PySCF molecule object
200
  pyscf_elements = [atom.GetSymbol() for atom in mol.GetAtoms()]
201
  pyscf_coords = []
202
  for i in range(mol.GetNumAtoms()):
203
  pos = conf.GetAtomPosition(i)
 
204
  pyscf_coords.append([pos.x, pos.y, pos.z])
205
 
206
  pyscf_atoms = [(elem, coord) for elem, coord in zip(pyscf_elements, pyscf_coords)]
207
 
208
- # Create PySCF molecule - use small basis for speed
209
- pyscf_mole = gto.Mole(basis="sto-3g")
210
  pyscf_mole.atom = pyscf_atoms
 
 
 
211
  pyscf_mole.build()
212
 
213
- # Run fast DFT calculation
214
- mf = dft.RKS(pyscf_mole)
215
- mf.xc = 'b3lyp'
216
- mf.verbose = 0 # Suppress output
217
- mf.run()
218
 
219
- # Calculate Natural Atomic Orbitals (pre-NAOs for more localized orbitals)
220
- dm = mf.make_rdm1()
221
- naos = lo.nao.prenao(pyscf_mole, dm)
 
 
222
 
223
- # Create grid for orbital evaluation
224
- grid_resolution = 25 # Lower for speed
225
- margin = 3.0
226
 
227
- # Determine grid bounds
228
- all_x = [coord[0] for coord in pyscf_coords]
229
- all_y = [coord[1] for coord in pyscf_coords]
230
- all_z = [coord[2] for coord in pyscf_coords]
231
 
232
- x_min, x_max = min(all_x) - margin, max(all_x) + margin
233
- y_min, y_max = min(all_y) - margin, max(all_y) + margin
234
- z_min, z_max = min(all_z) - margin, max(all_z) + margin
235
 
236
- # Create meshgrid
237
- xs = np.linspace(x_min, x_max, grid_resolution)
238
- ys = np.linspace(y_min, y_max, grid_resolution)
239
- zs = np.linspace(z_min, z_max, grid_resolution)
240
- grid_x, grid_y, grid_z = np.meshgrid(xs, ys, zs, indexing='ij')
241
 
242
- # Flatten grid for evaluation
243
- grid_coords = np.column_stack([grid_x.ravel(), grid_y.ravel(), grid_z.ravel()])
244
 
245
- # Evaluate a few representative valence orbitals
246
- # Select orbitals around HOMO (Highest Occupied Molecular Orbital)
247
- n_orbitals_to_show = min(3, naos.shape[1]) # Show up to 3 orbitals
248
- start_orbital = max(0, naos.shape[1] - 5) # Start near valence orbitals
 
249
 
250
- for orbital_idx in range(start_orbital, min(start_orbital + n_orbitals_to_show, naos.shape[1])):
251
- # Evaluate orbital on grid
252
- ao_values = pyscf_mole.eval_gto('GTOval_sph', grid_coords)
253
- orbital_values = np.dot(ao_values, naos[:, orbital_idx])
254
- orbital_grid = orbital_values.reshape(grid_x.shape)
255
 
256
- # Create isosurface for positive lobe
257
- isoval_positive = 0.02
258
- try:
259
- from skimage import measure
260
- verts_pos, faces_pos, _, _ = measure.marching_cubes(orbital_grid, level=isoval_positive)
261
 
262
- # Transform vertices to real coordinates
263
- verts_pos[:, 0] = x_min + verts_pos[:, 0] * (x_max - x_min) / (grid_resolution - 1)
264
- verts_pos[:, 1] = y_min + verts_pos[:, 1] * (y_max - y_min) / (grid_resolution - 1)
265
- verts_pos[:, 2] = z_min + verts_pos[:, 2] * (z_max - z_min) / (grid_resolution - 1)
 
266
 
267
- orbital_trace_pos = go.Mesh3d(
268
- x=verts_pos[:, 0], y=verts_pos[:, 1], z=verts_pos[:, 2],
269
- i=faces_pos[:, 0], j=faces_pos[:, 1], k=faces_pos[:, 2],
270
- color='blue',
271
- opacity=0.3,
272
- name=f'Orbital {orbital_idx+1} (+)',
273
- hoverinfo='skip'
 
 
 
 
274
  )
275
- data_traces.append(orbital_trace_pos)
276
- except:
277
- pass
278
-
279
- # Create isosurface for negative lobe
280
- isoval_negative = -0.02
281
- try:
282
- verts_neg, faces_neg, _, _ = measure.marching_cubes(orbital_grid, level=isoval_negative)
283
 
284
- # Transform vertices to real coordinates
285
- verts_neg[:, 0] = x_min + verts_neg[:, 0] * (x_max - x_min) / (grid_resolution - 1)
286
- verts_neg[:, 1] = y_min + verts_neg[:, 1] * (y_max - y_min) / (grid_resolution - 1)
287
- verts_neg[:, 2] = z_min + verts_neg[:, 2] * (z_max - z_min) / (grid_resolution - 1)
288
 
289
- orbital_trace_neg = go.Mesh3d(
290
- x=verts_neg[:, 0], y=verts_neg[:, 1], z=verts_neg[:, 2],
291
- i=faces_neg[:, 0], j=faces_neg[:, 1], k=faces_neg[:, 2],
292
- color='red',
293
- opacity=0.3,
294
- name=f'Orbital {orbital_idx+1} (-)',
295
- hoverinfo='skip'
 
 
 
 
296
  )
297
- data_traces.append(orbital_trace_neg)
298
- except:
299
- pass
300
 
 
 
 
301
  except Exception as e:
302
- # If orbital calculation fails, add a note
303
- print(f"Orbital calculation failed: {str(e)}")
304
- # Fall back to simple message - orbitals are computationally expensive
305
 
306
  # Create figure
307
  fig = go.Figure(data=data_traces)
@@ -357,8 +363,8 @@ name_interface = gr.Interface(
357
 
358
  # Create Blocks interface for molecule viewer with autocomplete
359
  with gr.Blocks() as molecule_3d_block:
360
- gr.Markdown("## 🔬 3D Molecule Viewer with Quantum Orbitals")
361
- gr.Markdown("Enter a chemical name to view its 2D and 3D structure. Toggle orbitals to see **real quantum mechanical Natural Atomic Orbitals (NAOs)** computed with PySCF!")
362
 
363
  with gr.Row():
364
  with gr.Column():
@@ -373,9 +379,9 @@ with gr.Blocks() as molecule_3d_block:
373
  filterable=True
374
  )
375
  orbital_checkbox = gr.Checkbox(
376
- label="Show Quantum Orbitals (NAOs)",
377
  value=False,
378
- info="Compute and display real Natural Atomic Orbitals using quantum chemistry (DFT/B3LYP)"
379
  )
380
  submit_btn = gr.Button("Generate 3D Molecule", variant="primary")
381
 
 
194
  if show_orbitals:
195
  try:
196
  import numpy as np
197
+ from pyscf import gto, scf
198
 
199
  # Build PySCF molecule object
200
  pyscf_elements = [atom.GetSymbol() for atom in mol.GetAtoms()]
201
  pyscf_coords = []
202
  for i in range(mol.GetNumAtoms()):
203
  pos = conf.GetAtomPosition(i)
204
+ # Convert to Angstrom (PySCF uses Angstrom)
205
  pyscf_coords.append([pos.x, pos.y, pos.z])
206
 
207
  pyscf_atoms = [(elem, coord) for elem, coord in zip(pyscf_elements, pyscf_coords)]
208
 
209
+ # Create PySCF molecule with minimal basis
210
+ pyscf_mole = gto.Mole()
211
  pyscf_mole.atom = pyscf_atoms
212
+ pyscf_mole.basis = 'sto-3g'
213
+ pyscf_mole.unit = 'A'
214
+ pyscf_mole.verbose = 0
215
  pyscf_mole.build()
216
 
217
+ # Run Hartree-Fock calculation (faster than DFT)
218
+ mf = scf.RHF(pyscf_mole)
219
+ mf.verbose = 0
220
+ mf.kernel()
 
221
 
222
+ # Get HOMO and LUMO indices
223
+ n_electrons = pyscf_mole.nelectron
224
+ n_occ = n_electrons // 2
225
+ homo_idx = n_occ - 1
226
+ lumo_idx = n_occ
227
 
228
+ # Create a coarser grid for visualization
229
+ margin = 2.5
230
+ grid_size = 30
231
 
232
+ x_coords_np = np.array(x_coords)
233
+ y_coords_np = np.array(y_coords)
234
+ z_coords_np = np.array(z_coords)
 
235
 
236
+ x_min, x_max = x_coords_np.min() - margin, x_coords_np.max() + margin
237
+ y_min, y_max = y_coords_np.min() - margin, y_coords_np.max() + margin
238
+ z_min, z_max = z_coords_np.min() - margin, z_coords_np.max() + margin
239
 
240
+ x_grid = np.linspace(x_min, x_max, grid_size)
241
+ y_grid = np.linspace(y_min, y_max, grid_size)
242
+ z_grid = np.linspace(z_min, z_max, grid_size)
 
 
243
 
244
+ coords_grid = np.array(np.meshgrid(x_grid, y_grid, z_grid, indexing='ij'))
245
+ grid_points = coords_grid.reshape(3, -1).T
246
 
247
+ # Evaluate HOMO orbital on grid
248
+ ao_value = pyscf_mole.eval_gto('GTOval_sph', grid_points)
249
+ mo_coeff = mf.mo_coeff[:, homo_idx]
250
+ homo_values = np.dot(ao_value, mo_coeff)
251
+ homo_grid = homo_values.reshape(grid_size, grid_size, grid_size)
252
 
253
+ # Create isosurfaces using marching cubes
254
+ try:
255
+ from skimage import measure
 
 
256
 
257
+ # HOMO positive isosurface
258
+ iso_val = 0.03
259
+ if np.abs(homo_grid).max() > iso_val:
260
+ verts, faces, _, _ = measure.marching_cubes(homo_grid, level=iso_val)
 
261
 
262
+ # Scale vertices to actual coordinates
263
+ verts_scaled = np.zeros_like(verts)
264
+ verts_scaled[:, 0] = x_min + verts[:, 0] * (x_max - x_min) / (grid_size - 1)
265
+ verts_scaled[:, 1] = y_min + verts[:, 1] * (y_max - y_min) / (grid_size - 1)
266
+ verts_scaled[:, 2] = z_min + verts[:, 2] * (z_max - z_min) / (grid_size - 1)
267
 
268
+ homo_pos_trace = go.Mesh3d(
269
+ x=verts_scaled[:, 0],
270
+ y=verts_scaled[:, 1],
271
+ z=verts_scaled[:, 2],
272
+ i=faces[:, 0],
273
+ j=faces[:, 1],
274
+ k=faces[:, 2],
275
+ color='lightblue',
276
+ opacity=0.4,
277
+ name='HOMO (+)',
278
+ hoverinfo='name'
279
  )
280
+ data_traces.append(homo_pos_trace)
281
+
282
+ # HOMO negative isosurface
283
+ verts_neg, faces_neg, _, _ = measure.marching_cubes(homo_grid, level=-iso_val)
 
 
 
 
284
 
285
+ verts_neg_scaled = np.zeros_like(verts_neg)
286
+ verts_neg_scaled[:, 0] = x_min + verts_neg[:, 0] * (x_max - x_min) / (grid_size - 1)
287
+ verts_neg_scaled[:, 1] = y_min + verts_neg[:, 1] * (y_max - y_min) / (grid_size - 1)
288
+ verts_neg_scaled[:, 2] = z_min + verts_neg[:, 2] * (z_max - z_min) / (grid_size - 1)
289
 
290
+ homo_neg_trace = go.Mesh3d(
291
+ x=verts_neg_scaled[:, 0],
292
+ y=verts_neg_scaled[:, 1],
293
+ z=verts_neg_scaled[:, 2],
294
+ i=faces_neg[:, 0],
295
+ j=faces_neg[:, 1],
296
+ k=faces_neg[:, 2],
297
+ color='salmon',
298
+ opacity=0.4,
299
+ name='HOMO (-)',
300
+ hoverinfo='name'
301
  )
302
+ data_traces.append(homo_neg_trace)
 
 
303
 
304
+ except Exception as e:
305
+ print(f"Isosurface generation failed: {e}")
306
+
307
  except Exception as e:
308
+ print(f"Orbital calculation failed: {e}")
309
+ import traceback
310
+ traceback.print_exc()
311
 
312
  # Create figure
313
  fig = go.Figure(data=data_traces)
 
363
 
364
  # Create Blocks interface for molecule viewer with autocomplete
365
  with gr.Blocks() as molecule_3d_block:
366
+ gr.Markdown("## 🔬 3D Molecule Viewer with HOMO/LUMO Orbitals")
367
+ gr.Markdown("Enter a chemical name to view its 2D and 3D structure. Toggle orbitals to see **HOMO (Highest Occupied Molecular Orbital)** computed with quantum chemistry!")
368
 
369
  with gr.Row():
370
  with gr.Column():
 
379
  filterable=True
380
  )
381
  orbital_checkbox = gr.Checkbox(
382
+ label="Show HOMO Orbital",
383
  value=False,
384
+ info="Calculate and display HOMO (Highest Occupied Molecular Orbital) using Hartree-Fock"
385
  )
386
  submit_btn = gr.Button("Generate 3D Molecule", variant="primary")
387