Spaces:
Runtime error
Runtime error
Make orbital tab optional when Psikit unavailable
Browse files- app.py +47 -33
- requirements.txt +1 -4
app.py
CHANGED
|
@@ -87,25 +87,32 @@ def smiles_to_name(smiles: str) -> str:
|
|
| 87 |
|
| 88 |
|
| 89 |
def smiles_to_molecular_orbitals(smiles: str) -> str:
|
| 90 |
-
"""Generate HOMO/LUMO isosurfaces using Psikit
|
| 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
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
try:
|
| 101 |
-
import py3Dmol
|
| 102 |
-
except ImportError
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
| 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()
|
|
@@ -113,36 +120,43 @@ def smiles_to_molecular_orbitals(smiles: str) -> str:
|
|
| 113 |
|
| 114 |
cube_candidates = sorted(Path(tmpdir).glob("Psi_a_*A.cube"))
|
| 115 |
if not cube_candidates:
|
| 116 |
-
|
|
|
|
|
|
|
| 117 |
|
| 118 |
mol_block = Chem.MolToMolBlock(pk.mol)
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"""
|
|
|
|
| 87 |
|
| 88 |
|
| 89 |
def smiles_to_molecular_orbitals(smiles: str) -> str:
|
| 90 |
+
"""Generate HOMO/LUMO isosurfaces using Psikit, when available."""
|
| 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 # type: ignore[import]
|
| 97 |
+
except ImportError:
|
| 98 |
+
return (
|
| 99 |
+
"<p><strong>Molecular orbital rendering requires Psikit + Psi4.</strong> "
|
| 100 |
+
"Install them locally with <code>pip install psikit psi4 py3Dmol</code> "
|
| 101 |
+
"and run this app on your machine.</p>"
|
| 102 |
+
)
|
| 103 |
|
| 104 |
try:
|
| 105 |
+
import py3Dmol # type: ignore[import]
|
| 106 |
+
except ImportError:
|
| 107 |
+
return (
|
| 108 |
+
"<p><strong>py3Dmol is missing.</strong> Install it with <code>pip install py3Dmol</code> "
|
| 109 |
+
"to view orbital isosurfaces.</p>"
|
| 110 |
+
)
|
| 111 |
|
| 112 |
with tempfile.TemporaryDirectory() as tmpdir:
|
| 113 |
original_cwd = os.getcwd()
|
| 114 |
+
os.chdir(tmpdir)
|
| 115 |
try:
|
|
|
|
| 116 |
pk = Psikit()
|
| 117 |
pk.read_from_smiles(smiles)
|
| 118 |
pk.optimize()
|
|
|
|
| 120 |
|
| 121 |
cube_candidates = sorted(Path(tmpdir).glob("Psi_a_*A.cube"))
|
| 122 |
if not cube_candidates:
|
| 123 |
+
return (
|
| 124 |
+
"<p>Psikit did not produce cube files. Try a smaller molecule or different conformer.</p>"
|
| 125 |
+
)
|
| 126 |
|
| 127 |
mol_block = Chem.MolToMolBlock(pk.mol)
|
| 128 |
+
|
| 129 |
+
html_sections: list[str] = []
|
| 130 |
+
labels = ["HOMO", "LUMO"]
|
| 131 |
+
for cube_file, label in zip(cube_candidates[:2], labels):
|
| 132 |
+
cube_data = cube_file.read_text()
|
| 133 |
+
view = py3Dmol.view(width=500, height=400)
|
| 134 |
+
view.addModel(mol_block, "mol")
|
| 135 |
+
view.setStyle({"stick": {}})
|
| 136 |
+
isoval = 0.02
|
| 137 |
+
# Use the same volumetric data for positive/negative isosurfaces.
|
| 138 |
+
view.addVolumetricData(
|
| 139 |
+
cube_data,
|
| 140 |
+
"cube",
|
| 141 |
+
{"isoval": isoval, "color": "blue", "opacity": 0.6},
|
| 142 |
+
)
|
| 143 |
+
view.addVolumetricData(
|
| 144 |
+
cube_data,
|
| 145 |
+
"cube",
|
| 146 |
+
{"isoval": -isoval, "color": "red", "opacity": 0.6},
|
| 147 |
+
)
|
| 148 |
+
view.zoomTo()
|
| 149 |
+
html_sections.append(f"<h3>{label}</h3>" + view._make_html())
|
| 150 |
+
|
| 151 |
+
if not html_sections:
|
| 152 |
+
return "<p>Could not prepare HOMO/LUMO visualizations.</p>"
|
| 153 |
+
|
| 154 |
+
return "".join(html_sections)
|
| 155 |
+
except Exception as exc: # pragma: no cover - runtime heavy
|
| 156 |
+
return f"<p>Unable to compute molecular orbitals: {exc}</p>"
|
| 157 |
finally:
|
| 158 |
os.chdir(original_cwd)
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
def name_to_3d_molecule(name: str) -> str:
|
| 162 |
"""Convert chemical name to 3D molecule visualization"""
|
requirements.txt
CHANGED
|
@@ -3,7 +3,4 @@ rdkit
|
|
| 3 |
gradio==4.44.1
|
| 4 |
huggingface_hub==0.19.4
|
| 5 |
cirpy
|
| 6 |
-
pubchempy
|
| 7 |
-
psikit
|
| 8 |
-
psi4
|
| 9 |
-
py3Dmol
|
|
|
|
| 3 |
gradio==4.44.1
|
| 4 |
huggingface_hub==0.19.4
|
| 5 |
cirpy
|
| 6 |
+
pubchempy
|
|
|
|
|
|
|
|
|