Nanny7 commited on
Commit
e7217e1
·
1 Parent(s): 26cfee5

Add molecular orbital visualization tab

Browse files
Files changed (2) hide show
  1. app.py +80 -1
  2. requirements.txt +4 -1
app.py CHANGED
@@ -4,6 +4,9 @@ from rdkit.Chem import Descriptors, Draw, AllChem
4
  import cirpy
5
  import pubchempy as pcp
6
  from urllib.error import HTTPError, URLError
 
 
 
7
 
8
 
9
  # RDKit API with multiple endpoints
@@ -83,6 +86,64 @@ def smiles_to_name(smiles: str) -> str:
83
  return f"No name available. Canonical SMILES: {canonical_smiles}"
84
 
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  def name_to_3d_molecule(name: str) -> str:
87
  """Convert chemical name to 3D molecule visualization"""
88
  try:
@@ -153,6 +214,14 @@ smiles_to_name_interface = gr.Interface(
153
  description="Convert a SMILES string to a chemical name.",
154
  )
155
 
 
 
 
 
 
 
 
 
156
  name_interface = gr.Interface(
157
  fn=name_to_smiles,
158
  inputs=gr.Textbox(label="Chemical Name", placeholder="e.g., aspirin, caffeine, benzene"),
@@ -197,10 +266,20 @@ molecule_3d_interface = gr.Interface(
197
 
198
 
199
  demo = gr.TabbedInterface(
200
- [name_interface, molecule_3d_interface, smiles_interface, smiles_to_name_interface, mw_interface, logp_interface, tpsa_interface],
 
 
 
 
 
 
 
 
 
201
  [
202
  "Name to SMILES",
203
  "3D Molecule Viewer",
 
204
  "SMILES to Canonical",
205
  "SMILES to Name",
206
  "Molecular Weight",
 
4
  import cirpy
5
  import pubchempy as pcp
6
  from urllib.error import HTTPError, URLError
7
+ import os
8
+ import tempfile
9
+ from pathlib import Path
10
 
11
 
12
  # RDKit API with multiple endpoints
 
86
  return f"No name available. Canonical SMILES: {canonical_smiles}"
87
 
88
 
89
+ def smiles_to_molecular_orbitals(smiles: str) -> str:
90
+ """Generate HOMO/LUMO isosurfaces using Psikit and render with py3Dmol."""
91
+ mol = _mol_from_smiles(smiles)
92
+ if mol.GetNumAtoms() > 30:
93
+ raise gr.Error("Please provide a molecule with 30 atoms or fewer for orbital visualization.")
94
+
95
+ try:
96
+ from psikit import Psikit
97
+ except ImportError as exc: # pragma: no cover - runtime dependency
98
+ raise gr.Error("Psikit is required for molecular orbital visualization.") from exc
99
+
100
+ try:
101
+ import py3Dmol
102
+ except ImportError as exc: # pragma: no cover - runtime dependency
103
+ raise gr.Error("py3Dmol is required for molecular orbital visualization.") from exc
104
+
105
+ with tempfile.TemporaryDirectory() as tmpdir:
106
+ original_cwd = os.getcwd()
107
+ try:
108
+ os.chdir(tmpdir)
109
+ pk = Psikit()
110
+ pk.read_from_smiles(smiles)
111
+ pk.optimize()
112
+ pk.getMOview()
113
+
114
+ cube_candidates = sorted(Path(tmpdir).glob("Psi_a_*A.cube"))
115
+ if not cube_candidates:
116
+ raise gr.Error("Failed to generate molecular orbital cube files. Try a smaller molecule.")
117
+
118
+ mol_block = Chem.MolToMolBlock(pk.mol)
119
+ except Exception as exc:
120
+ raise gr.Error(f"Unable to compute molecular orbitals: {exc}") from exc
121
+ finally:
122
+ os.chdir(original_cwd)
123
+
124
+ def _render_cube(cube_path: Path, title: str) -> str:
125
+ cube_data = cube_path.read_text()
126
+ view = py3Dmol.view(width=500, height=400)
127
+ view.addModel(mol_block, "mol")
128
+ view.setStyle({"stick": {}})
129
+ isoval = 0.02
130
+ view.addVolumetricData(cube_data, "cube", {"isoval": isoval, "color": "blue", "opacity": 0.6})
131
+ view.addVolumetricData(cube_data, "cube", {"isoval": -isoval, "color": "red", "opacity": 0.6})
132
+ view.zoomTo()
133
+ return f"<h3>{title}</h3>" + view._make_html()
134
+
135
+ # First cube is HOMO, second is LUMO following psikit naming convention.
136
+ html_sections = []
137
+ labels = ["HOMO", "LUMO"]
138
+ for cube_file, label in zip(cube_candidates[:2], labels):
139
+ html_sections.append(_render_cube(cube_file, label))
140
+
141
+ if not html_sections:
142
+ raise gr.Error("Could not prepare HOMO/LUMO visualizations.")
143
+
144
+ return "".join(html_sections)
145
+
146
+
147
  def name_to_3d_molecule(name: str) -> str:
148
  """Convert chemical name to 3D molecule visualization"""
149
  try:
 
214
  description="Convert a SMILES string to a chemical name.",
215
  )
216
 
217
+ orbital_interface = gr.Interface(
218
+ fn=smiles_to_molecular_orbitals,
219
+ inputs=gr.Textbox(label="SMILES", placeholder="e.g., CC(=O)O"),
220
+ outputs=gr.HTML(label="Molecular Orbitals"),
221
+ api_name="smiles_to_mo",
222
+ description="Generate HOMO/LUMO isosurfaces using Psikit (CPU-intensive).",
223
+ )
224
+
225
  name_interface = gr.Interface(
226
  fn=name_to_smiles,
227
  inputs=gr.Textbox(label="Chemical Name", placeholder="e.g., aspirin, caffeine, benzene"),
 
266
 
267
 
268
  demo = gr.TabbedInterface(
269
+ [
270
+ name_interface,
271
+ molecule_3d_interface,
272
+ orbital_interface,
273
+ smiles_interface,
274
+ smiles_to_name_interface,
275
+ mw_interface,
276
+ logp_interface,
277
+ tpsa_interface,
278
+ ],
279
  [
280
  "Name to SMILES",
281
  "3D Molecule Viewer",
282
+ "Molecular Orbitals",
283
  "SMILES to Canonical",
284
  "SMILES to Name",
285
  "Molecular Weight",
requirements.txt CHANGED
@@ -3,4 +3,7 @@ rdkit
3
  gradio==4.44.1
4
  huggingface_hub==0.19.4
5
  cirpy
6
- pubchempy
 
 
 
 
3
  gradio==4.44.1
4
  huggingface_hub==0.19.4
5
  cirpy
6
+ pubchempy
7
+ psikit
8
+ psi4
9
+ py3Dmol