Spaces:
Running
on
Zero
Running
on
Zero
removed RDkit and molgallery for display no going back and just using plotly by hand
Browse files- app.py +20 -30
- molecules.json +100 -3
- requirements.txt +2 -4
- visualization.py +132 -157
app.py
CHANGED
@@ -57,6 +57,11 @@ import json
|
|
57 |
import os
|
58 |
import logging
|
59 |
from logging.handlers import RotatingFileHandler
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
# Configure logging
|
62 |
os.makedirs('logs', exist_ok=True)
|
@@ -134,13 +139,10 @@ def load_molecules():
|
|
134 |
return {}
|
135 |
|
136 |
# Now import the rest of the modules
|
137 |
-
import
|
138 |
import numpy as np
|
139 |
from quantum_utils import run_vqe_simulation
|
140 |
from visualization import plot_convergence, create_molecule_viewer, format_molecule_params
|
141 |
-
from gradio_molgallery3d import MolGallery3D
|
142 |
-
|
143 |
-
#Todo later add this working client stuff to fix timeouts client = Client("your-space", headers={"X-IP-Token": request.headers['x-ip-token']})
|
144 |
|
145 |
# Add immediate logging
|
146 |
print("Imported all modules successfully", file=sys.stderr, flush=True)
|
@@ -207,15 +209,12 @@ def create_interface():
|
|
207 |
|
208 |
def set_client_for_session(request: gr.Request):
|
209 |
"""Initialize client with user's IP token to handle rate limiting."""
|
210 |
-
# from https://www.gradio.app/docs/python-client/using-zero-gpu-spaces
|
211 |
try:
|
212 |
-
x_ip_token = request.headers['x-ip-token']
|
213 |
return Client("A19grey/Cuda-Quantum-Molecular-Playground", headers={"x-ip-token": x_ip_token})
|
214 |
except Exception as e:
|
215 |
logger.error(f"Error setting client: {e}")
|
216 |
return None
|
217 |
-
|
218 |
-
|
219 |
|
220 |
# Load available molecules
|
221 |
molecules = load_molecules()
|
@@ -246,23 +245,20 @@ def create_interface():
|
|
246 |
*Inspired by the [NVIDIA CUDA-Q VQE Example](https://nvidia.github.io/cuda-quantum/latest/applications/python/vqe_advanced.html)*
|
247 |
""")
|
248 |
|
249 |
-
client = gr.State()
|
250 |
-
|
251 |
-
state = gr.State({}) # Store current molecule data
|
252 |
|
253 |
# Top section: 3 columns
|
254 |
with gr.Row():
|
255 |
with gr.Column(scale=1):
|
256 |
-
# Molecule selection controls
|
257 |
molecule_choice = gr.Dropdown(
|
258 |
choices=list(molecules.keys()),
|
259 |
-
value=None,
|
260 |
label="Select Molecule",
|
261 |
interactive=True,
|
262 |
allow_custom_value=False
|
263 |
)
|
264 |
|
265 |
-
# Get scale range from selected molecule
|
266 |
default_molecule = molecules[list(molecules.keys())[0]]
|
267 |
scale_factor = gr.Slider(
|
268 |
minimum=default_molecule['scale_range']['min'],
|
@@ -274,19 +270,15 @@ def create_interface():
|
|
274 |
)
|
275 |
|
276 |
with gr.Column(scale=1):
|
277 |
-
# Molecule parameters display
|
278 |
params_display = gr.HTML(
|
279 |
label="Molecule Parameters",
|
280 |
value="<div>Select a molecule to view its parameters</div>"
|
281 |
)
|
282 |
|
283 |
with gr.Column(scale=1):
|
284 |
-
|
285 |
-
molecule_viz = MolGallery3D(
|
286 |
-
columns=1,
|
287 |
-
height=400, # Reduced height to match other elements
|
288 |
label="3D Molecule Viewer",
|
289 |
-
|
290 |
)
|
291 |
|
292 |
# Middle section: Buttons and results in 2 columns
|
@@ -463,24 +455,22 @@ def create_interface():
|
|
463 |
step_size = mol_data.get('scale_range', {}).get('step', 0.05)
|
464 |
logger.debug(f"Scale parameters - min: {min_scale}, max: {max_scale}, default: {default_scale}")
|
465 |
|
466 |
-
# Create molecule visualization
|
467 |
logger.info(f"Creating molecule visualization for {molecule_choice}")
|
468 |
-
|
469 |
-
if
|
470 |
logger.error(f"No molecule visualization created for {molecule_choice}")
|
471 |
-
|
472 |
-
else:
|
473 |
-
logger.info(f"Successfully created visualization with {len(mol_list)} molecules")
|
474 |
|
475 |
return [
|
476 |
-
params_html,
|
477 |
-
gr.update(
|
478 |
minimum=min_scale,
|
479 |
maximum=max_scale,
|
480 |
value=default_scale,
|
481 |
step=step_size
|
482 |
),
|
483 |
-
|
484 |
]
|
485 |
except Exception as e:
|
486 |
logger.error(f"Error updating molecule info: {str(e)}", exc_info=True)
|
@@ -492,7 +482,7 @@ def create_interface():
|
|
492 |
value=1.0,
|
493 |
step=0.05
|
494 |
),
|
495 |
-
|
496 |
]
|
497 |
|
498 |
# Update parameters and 3D view when molecule changes
|
|
|
57 |
import os
|
58 |
import logging
|
59 |
from logging.handlers import RotatingFileHandler
|
60 |
+
import gradio as gr
|
61 |
+
import numpy as np
|
62 |
+
from quantum_utils import run_vqe_simulation
|
63 |
+
from visualization import plot_convergence, create_molecule_viewer, format_molecule_params
|
64 |
+
import plotly.graph_objects as go
|
65 |
|
66 |
# Configure logging
|
67 |
os.makedirs('logs', exist_ok=True)
|
|
|
139 |
return {}
|
140 |
|
141 |
# Now import the rest of the modules
|
142 |
+
print("about to import numpy and quantum_utils", file=sys.stderr, flush=True)
|
143 |
import numpy as np
|
144 |
from quantum_utils import run_vqe_simulation
|
145 |
from visualization import plot_convergence, create_molecule_viewer, format_molecule_params
|
|
|
|
|
|
|
146 |
|
147 |
# Add immediate logging
|
148 |
print("Imported all modules successfully", file=sys.stderr, flush=True)
|
|
|
209 |
|
210 |
def set_client_for_session(request: gr.Request):
|
211 |
"""Initialize client with user's IP token to handle rate limiting."""
|
|
|
212 |
try:
|
213 |
+
x_ip_token = request.headers['x-ip-token']
|
214 |
return Client("A19grey/Cuda-Quantum-Molecular-Playground", headers={"x-ip-token": x_ip_token})
|
215 |
except Exception as e:
|
216 |
logger.error(f"Error setting client: {e}")
|
217 |
return None
|
|
|
|
|
218 |
|
219 |
# Load available molecules
|
220 |
molecules = load_molecules()
|
|
|
245 |
*Inspired by the [NVIDIA CUDA-Q VQE Example](https://nvidia.github.io/cuda-quantum/latest/applications/python/vqe_advanced.html)*
|
246 |
""")
|
247 |
|
248 |
+
client = gr.State()
|
249 |
+
state = gr.State({})
|
|
|
250 |
|
251 |
# Top section: 3 columns
|
252 |
with gr.Row():
|
253 |
with gr.Column(scale=1):
|
|
|
254 |
molecule_choice = gr.Dropdown(
|
255 |
choices=list(molecules.keys()),
|
256 |
+
value=None,
|
257 |
label="Select Molecule",
|
258 |
interactive=True,
|
259 |
allow_custom_value=False
|
260 |
)
|
261 |
|
|
|
262 |
default_molecule = molecules[list(molecules.keys())[0]]
|
263 |
scale_factor = gr.Slider(
|
264 |
minimum=default_molecule['scale_range']['min'],
|
|
|
270 |
)
|
271 |
|
272 |
with gr.Column(scale=1):
|
|
|
273 |
params_display = gr.HTML(
|
274 |
label="Molecule Parameters",
|
275 |
value="<div>Select a molecule to view its parameters</div>"
|
276 |
)
|
277 |
|
278 |
with gr.Column(scale=1):
|
279 |
+
molecule_viz = gr.Plot(
|
|
|
|
|
|
|
280 |
label="3D Molecule Viewer",
|
281 |
+
show_label=True
|
282 |
)
|
283 |
|
284 |
# Middle section: Buttons and results in 2 columns
|
|
|
455 |
step_size = mol_data.get('scale_range', {}).get('step', 0.05)
|
456 |
logger.debug(f"Scale parameters - min: {min_scale}, max: {max_scale}, default: {default_scale}")
|
457 |
|
458 |
+
# Create molecule visualization using Plotly
|
459 |
logger.info(f"Creating molecule visualization for {molecule_choice}")
|
460 |
+
mol_plot = create_molecule_viewer(molecule_choice, default_scale)
|
461 |
+
if mol_plot is None:
|
462 |
logger.error(f"No molecule visualization created for {molecule_choice}")
|
463 |
+
mol_plot = go.Figure() # Empty figure as fallback
|
|
|
|
|
464 |
|
465 |
return [
|
466 |
+
params_html,
|
467 |
+
gr.update(
|
468 |
minimum=min_scale,
|
469 |
maximum=max_scale,
|
470 |
value=default_scale,
|
471 |
step=step_size
|
472 |
),
|
473 |
+
mol_plot
|
474 |
]
|
475 |
except Exception as e:
|
476 |
logger.error(f"Error updating molecule info: {str(e)}", exc_info=True)
|
|
|
482 |
value=1.0,
|
483 |
step=0.05
|
484 |
),
|
485 |
+
go.Figure() # Empty figure as fallback
|
486 |
]
|
487 |
|
488 |
# Update parameters and 3D view when molecule changes
|
molecules.json
CHANGED
@@ -48,7 +48,104 @@
|
|
48 |
],
|
49 |
"description": "Triatomic water molecule with bent geometry. Scale factor affects the overall size of the molecule."
|
50 |
},
|
51 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
"name": "Methane",
|
53 |
"enabled": 0,
|
54 |
"formula": "CH₄",
|
@@ -77,7 +174,7 @@
|
|
77 |
},
|
78 |
"CO": {
|
79 |
"name": "Carbon monoxide",
|
80 |
-
"enabled":
|
81 |
"formula": "CO",
|
82 |
"smiles": "[C-]#[O+]",
|
83 |
"basis": "sto-3g",
|
@@ -101,7 +198,7 @@
|
|
101 |
},
|
102 |
"N2": {
|
103 |
"name": "Nitrogen molecule",
|
104 |
-
"enabled":
|
105 |
"formula": "N₂",
|
106 |
"smiles": "N#N",
|
107 |
"basis": "sto-3g",
|
|
|
48 |
],
|
49 |
"description": "Triatomic water molecule with bent geometry. Scale factor affects the overall size of the molecule."
|
50 |
},
|
51 |
+
"BeH2": {
|
52 |
+
"name": "Beryllium Hydride",
|
53 |
+
"enabled": 1,
|
54 |
+
"formula": "BeH₂",
|
55 |
+
"smiles": "[BeH2]",
|
56 |
+
"basis": "sto-3g",
|
57 |
+
"multiplicity": 1,
|
58 |
+
"charge": 0,
|
59 |
+
"spatial_orbitals": 7,
|
60 |
+
"electron_count": 4,
|
61 |
+
"default_scale": 1.0,
|
62 |
+
"GPU_time": 90,
|
63 |
+
"iterations": 20,
|
64 |
+
"scale_range": {
|
65 |
+
"min": 0.5,
|
66 |
+
"max": 2.0,
|
67 |
+
"step": 0.05
|
68 |
+
},
|
69 |
+
"geometry_template": [
|
70 |
+
["Be", [0.0, 0.0, 0.0]],
|
71 |
+
["H", [1.33, 0.0, 0.0]],
|
72 |
+
["H", [-1.33, 0.0, 0.0]]
|
73 |
+
],
|
74 |
+
"description": "Linear beryllium hydride molecule with two hydrogen atoms symmetrically bonded. Scale factor affects the bond length."
|
75 |
+
},
|
76 |
+
"NH": {
|
77 |
+
"name": "Nitrogen Monohydride",
|
78 |
+
"enabled": 1,
|
79 |
+
"formula": "NH",
|
80 |
+
"smiles": "[NH]",
|
81 |
+
"basis": "sto-3g",
|
82 |
+
"multiplicity": 2,
|
83 |
+
"charge": 0,
|
84 |
+
"spatial_orbitals": 6,
|
85 |
+
"electron_count": 8,
|
86 |
+
"default_scale": 1.0,
|
87 |
+
"GPU_time": 90,
|
88 |
+
"iterations": 20,
|
89 |
+
"scale_range": {
|
90 |
+
"min": 0.5,
|
91 |
+
"max": 2.5,
|
92 |
+
"step": 0.05
|
93 |
+
},
|
94 |
+
"geometry_template": [
|
95 |
+
["N", [0.0, 0.0, 0.0]],
|
96 |
+
["H", [1.03, 0.0, 0.0]]
|
97 |
+
],
|
98 |
+
"description": "Diatomic nitrogen monohydride molecule, important in radical chemistry. Scale factor affects bond length."
|
99 |
+
},
|
100 |
+
"HF": {
|
101 |
+
"name": "Hydrogen Fluoride",
|
102 |
+
"enabled": 1,
|
103 |
+
"formula": "HF",
|
104 |
+
"smiles": "[FH]",
|
105 |
+
"basis": "sto-3g",
|
106 |
+
"multiplicity": 1,
|
107 |
+
"charge": 0,
|
108 |
+
"spatial_orbitals": 5,
|
109 |
+
"electron_count": 10,
|
110 |
+
"default_scale": 1.0,
|
111 |
+
"GPU_time": 90,
|
112 |
+
"iterations": 20,
|
113 |
+
"scale_range": {
|
114 |
+
"min": 0.5,
|
115 |
+
"max": 2.0,
|
116 |
+
"step": 0.05
|
117 |
+
},
|
118 |
+
"geometry_template": [
|
119 |
+
["H", [0.0, 0.0, 0.0]],
|
120 |
+
["F", [0.92, 0.0, 0.0]]
|
121 |
+
],
|
122 |
+
"description": "Diatomic hydrogen fluoride molecule, a simple polar molecule with strong dipole interactions. Scale factor affects bond length."
|
123 |
+
},
|
124 |
+
"LiH": {
|
125 |
+
"name": "Lithium Hydride",
|
126 |
+
"enabled": 1,
|
127 |
+
"formula": "LiH",
|
128 |
+
"smiles": "[LiH]",
|
129 |
+
"basis": "sto-3g",
|
130 |
+
"multiplicity": 1,
|
131 |
+
"charge": 0,
|
132 |
+
"spatial_orbitals": 4,
|
133 |
+
"electron_count": 4,
|
134 |
+
"default_scale": 1.0,
|
135 |
+
"GPU_time": 90,
|
136 |
+
"iterations": 20,
|
137 |
+
"scale_range": {
|
138 |
+
"min": 0.5,
|
139 |
+
"max": 2.5,
|
140 |
+
"step": 0.05
|
141 |
+
},
|
142 |
+
"geometry_template": [
|
143 |
+
["Li", [0.0, 0.0, 0.0]],
|
144 |
+
["H", [1.6, 0.0, 0.0]]
|
145 |
+
],
|
146 |
+
"description": "Diatomic lithium hydride molecule with ionic bonding properties. Scale factor affects bond length."
|
147 |
+
}
|
148 |
+
,"CH4": {
|
149 |
"name": "Methane",
|
150 |
"enabled": 0,
|
151 |
"formula": "CH₄",
|
|
|
174 |
},
|
175 |
"CO": {
|
176 |
"name": "Carbon monoxide",
|
177 |
+
"enabled": 0,
|
178 |
"formula": "CO",
|
179 |
"smiles": "[C-]#[O+]",
|
180 |
"basis": "sto-3g",
|
|
|
198 |
},
|
199 |
"N2": {
|
200 |
"name": "Nitrogen molecule",
|
201 |
+
"enabled": 0,
|
202 |
"formula": "N₂",
|
203 |
"smiles": "N#N",
|
204 |
"basis": "sto-3g",
|
requirements.txt
CHANGED
@@ -6,15 +6,13 @@ numpy==1.24.3
|
|
6 |
scipy==1.10.1
|
7 |
|
8 |
# Gradio and visualization components
|
9 |
-
|
10 |
-
|
11 |
-
gradio_molgallery3d>=0.0.4
|
12 |
|
13 |
# Plotting and visualization libraries
|
14 |
plotly==5.18.0
|
15 |
bokeh==3.2.2
|
16 |
matplotlib==3.7.1
|
17 |
-
rdkit==2023.09.1
|
18 |
|
19 |
# ASE for molecular geometry handling
|
20 |
ase==3.22.1
|
|
|
6 |
scipy==1.10.1
|
7 |
|
8 |
# Gradio and visualization components
|
9 |
+
gradio==4.44.1
|
10 |
+
gradio_client
|
|
|
11 |
|
12 |
# Plotting and visualization libraries
|
13 |
plotly==5.18.0
|
14 |
bokeh==3.2.2
|
15 |
matplotlib==3.7.1
|
|
|
16 |
|
17 |
# ASE for molecular geometry handling
|
18 |
ase==3.22.1
|
visualization.py
CHANGED
@@ -1,29 +1,70 @@
|
|
1 |
"""
|
2 |
visualization.py
|
3 |
-
Routines for plotting VQE results and molecular visualization using Plotly
|
4 |
"""
|
5 |
|
6 |
import plotly.graph_objects as go
|
7 |
import numpy as np
|
8 |
import logging
|
9 |
-
from rdkit import Chem
|
10 |
-
from rdkit.Chem import AllChem
|
11 |
-
from gradio_molgallery3d import MolGallery3D
|
12 |
import json
|
13 |
|
14 |
# Configure logging
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
logging.FileHandler('molecule_creation.log') # This will output to file
|
20 |
-
])
|
21 |
logger = logging.getLogger(__name__)
|
22 |
-
logger.setLevel(logging.DEBUG)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
-
#
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
def format_molecule_params(molecule_data: dict) -> str:
|
29 |
"""
|
@@ -82,7 +123,6 @@ def plot_convergence(results):
|
|
82 |
# Handle both dictionary entries and direct energy values
|
83 |
for i, entry in enumerate(history):
|
84 |
if isinstance(entry, dict):
|
85 |
-
# Handle dictionary format
|
86 |
try:
|
87 |
iterations.append(entry.get('iteration', i))
|
88 |
energies.append(entry['energy'])
|
@@ -90,7 +130,6 @@ def plot_convergence(results):
|
|
90 |
logger.warning(f"Skipping invalid entry {i}: {str(e)}")
|
91 |
continue
|
92 |
else:
|
93 |
-
# Handle direct energy values
|
94 |
try:
|
95 |
iterations.append(i)
|
96 |
energies.append(float(entry))
|
@@ -146,16 +185,16 @@ def plot_convergence(results):
|
|
146 |
|
147 |
return fig
|
148 |
|
149 |
-
def create_molecule_viewer(molecule_id: str, scale_factor: float) ->
|
150 |
"""
|
151 |
-
Create a 3D visualization of the molecule using
|
152 |
-
|
153 |
-
The actual quantum calculations still use the real molecule structure.
|
154 |
-
Atoms are displayed as spheres with explicit bonds between them.
|
155 |
|
156 |
Args:
|
157 |
molecule_id: Molecule identifier (e.g., "H2")
|
158 |
scale_factor: Bond length scaling factor (1.0 = original size)
|
|
|
|
|
159 |
"""
|
160 |
logger.info(f"Creating 3D view for {molecule_id} with scale_factor={scale_factor}")
|
161 |
|
@@ -169,147 +208,83 @@ def create_molecule_viewer(molecule_id: str, scale_factor: float) -> MolGallery3
|
|
169 |
return None
|
170 |
|
171 |
molecule_data = molecules[molecule_id]
|
172 |
-
|
173 |
-
|
174 |
-
# Special handling for different molecules
|
175 |
-
mol = None
|
176 |
-
if molecule_id == "H2":
|
177 |
-
logger.debug("Creating H2 molecule (visualized as N2)")
|
178 |
-
# Create N2 for visualization instead of H2
|
179 |
-
mol = Chem.RWMol()
|
180 |
-
n1 = mol.AddAtom(Chem.Atom(7)) # Add first N atom
|
181 |
-
n2 = mol.AddAtom(Chem.Atom(7)) # Add second N atom
|
182 |
-
mol.AddBond(n1, n2, Chem.BondType.SINGLE) # Add single bond
|
183 |
-
mol = mol.GetMol()
|
184 |
-
Chem.SanitizeMol(mol)
|
185 |
-
logger.debug(f"N2 (for H2 viz) creation result: {mol is not None}")
|
186 |
-
|
187 |
-
elif molecule_id == "H2O":
|
188 |
-
logger.debug("Creating H2O molecule (visualized with N instead of H)")
|
189 |
-
# Create H2O using RWMol but with N instead of H
|
190 |
-
mol = Chem.RWMol()
|
191 |
-
|
192 |
-
# Add atoms with explicit valence
|
193 |
-
o = mol.AddAtom(Chem.Atom(8)) # Oxygen
|
194 |
-
oxygen = mol.GetAtomWithIdx(o)
|
195 |
-
oxygen.SetFormalCharge(0)
|
196 |
-
oxygen.SetNoImplicit(True)
|
197 |
-
|
198 |
-
n1 = mol.AddAtom(Chem.Atom(7)) # First nitrogen (replacing H)
|
199 |
-
n1_atom = mol.GetAtomWithIdx(n1)
|
200 |
-
n1_atom.SetFormalCharge(0)
|
201 |
-
n1_atom.SetNoImplicit(True)
|
202 |
-
|
203 |
-
n2 = mol.AddAtom(Chem.Atom(7)) # Second nitrogen (replacing H)
|
204 |
-
n2_atom = mol.GetAtomWithIdx(n2)
|
205 |
-
n2_atom.SetFormalCharge(0)
|
206 |
-
n2_atom.SetNoImplicit(True)
|
207 |
-
|
208 |
-
# Add bonds
|
209 |
-
mol.AddBond(o, n1, Chem.BondType.SINGLE)
|
210 |
-
mol.AddBond(o, n2, Chem.BondType.SINGLE)
|
211 |
-
|
212 |
-
# Convert to immutable molecule and sanitize
|
213 |
-
mol = mol.GetMol()
|
214 |
-
Chem.SanitizeMol(mol)
|
215 |
-
logger.debug(f"H2O (with N) creation result: {mol is not None}")
|
216 |
-
|
217 |
-
else:
|
218 |
-
# For other molecules, create from SMILES but replace H with N
|
219 |
-
logger.debug(f"Creating molecule from SMILES: {molecule_data['smiles']}")
|
220 |
-
mol = Chem.MolFromSmiles(molecule_data['smiles'])
|
221 |
-
logger.debug(f"Initial molecule creation result: {mol is not None}")
|
222 |
-
if mol is not None:
|
223 |
-
# Replace explicit hydrogens with nitrogen
|
224 |
-
mol = Chem.RWMol(mol)
|
225 |
-
for atom in mol.GetAtoms():
|
226 |
-
if atom.GetSymbol() == 'H':
|
227 |
-
atom.SetAtomicNum(7) # Change to nitrogen
|
228 |
-
mol = mol.GetMol()
|
229 |
-
Chem.SanitizeMol(mol)
|
230 |
-
logger.debug(f"Molecule with N replacing H result: {mol is not None}")
|
231 |
-
|
232 |
-
if mol is None:
|
233 |
-
logger.error(f"Failed to create molecule for {molecule_id}")
|
234 |
return None
|
235 |
|
236 |
-
|
237 |
-
for atom in mol.GetAtoms():
|
238 |
-
# Calculate valence before checking hydrogens
|
239 |
-
atom.UpdatePropertyCache()
|
240 |
-
logger.debug(f"Atom {atom.GetIdx()}: Symbol={atom.GetSymbol()}, " +
|
241 |
-
f"Valence={atom.GetTotalValence()}")
|
242 |
|
243 |
-
#
|
244 |
-
|
245 |
-
for
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
#
|
253 |
-
if
|
254 |
-
logger.debug("Using geometry template for 3D coordinates")
|
255 |
-
conf = Chem.Conformer(mol.GetNumAtoms())
|
256 |
-
for i, (atom_symbol, coords) in enumerate(molecule_data['geometry_template']):
|
257 |
-
# Scale coordinates by the scale factor
|
258 |
-
scaled_coords = np.array(coords) * scale_factor
|
259 |
-
conf.SetAtomPosition(i, scaled_coords)
|
260 |
-
mol.AddConformer(conf)
|
261 |
-
result = 0 # Template-based embedding always succeeds
|
262 |
-
else:
|
263 |
-
result = AllChem.EmbedMolecule(mol, randomSeed=42)
|
264 |
-
|
265 |
-
logger.debug(f"3D coordinate generation result (0=success, -1=failure): {result}")
|
266 |
-
|
267 |
-
if result == -1:
|
268 |
-
logger.debug("Standard embedding failed, trying with ETKDGv2")
|
269 |
-
params = AllChem.ETKDGv2()
|
270 |
-
result = AllChem.EmbedMolecule(mol, params, randomSeed=42)
|
271 |
-
logger.debug(f"ETKDGv2 embedding result: {result}")
|
272 |
-
|
273 |
-
if result == -1:
|
274 |
-
logger.error("Failed to generate 3D coordinates")
|
275 |
-
return None
|
276 |
-
|
277 |
-
# Scale the molecule coordinates if not using template
|
278 |
-
if 'geometry_template' not in molecule_data:
|
279 |
-
conf = mol.GetConformer()
|
280 |
-
positions = conf.GetPositions()
|
281 |
-
logger.debug(f"Generated 3D coordinates:\n{positions}")
|
282 |
-
centroid = np.mean(positions, axis=0)
|
283 |
-
logger.debug(f"Molecule centroid: {centroid}")
|
284 |
-
|
285 |
-
for i in range(mol.GetNumAtoms()):
|
286 |
-
pos = positions[i]
|
287 |
-
scaled_pos = centroid + (pos - centroid) * scale_factor
|
288 |
-
conf.SetAtomPosition(i, scaled_pos)
|
289 |
-
logger.debug(f"Atom {i} ({mol.GetAtomWithIdx(i).GetSymbol()}) scaled position: {scaled_pos}")
|
290 |
-
|
291 |
-
logger.info(f"Successfully created 3D coordinates for {molecule_id}")
|
292 |
-
|
293 |
-
# Verify final state
|
294 |
-
logger.debug("Final molecule state:")
|
295 |
-
logger.debug(f"Number of atoms: {mol.GetNumAtoms()}")
|
296 |
-
logger.debug(f"Number of bonds: {mol.GetNumBonds()}")
|
297 |
-
logger.debug(f"Has conformer: {mol.GetNumConformers() > 0}")
|
298 |
-
|
299 |
-
# Final sanitization check
|
300 |
-
Chem.SanitizeMol(mol)
|
301 |
-
|
302 |
-
# Set RDKit display properties for ball-and-stick appearance
|
303 |
-
for atom in mol.GetAtoms():
|
304 |
-
atom.SetProp('molAtomMapNumber', str(atom.GetIdx())) # This helps with atom visibility
|
305 |
-
|
306 |
-
# Return the molecule as a list as expected by MolGallery3D
|
307 |
-
return [mol]
|
308 |
|
309 |
-
|
310 |
-
|
311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
except Exception as e:
|
314 |
logger.error(f"Failed to create molecule viewer: {e}", exc_info=True)
|
315 |
return None
|
|
|
1 |
"""
|
2 |
visualization.py
|
3 |
+
Routines for plotting VQE results and molecular visualization using Plotly
|
4 |
"""
|
5 |
|
6 |
import plotly.graph_objects as go
|
7 |
import numpy as np
|
8 |
import logging
|
|
|
|
|
|
|
9 |
import json
|
10 |
|
11 |
# Configure logging
|
12 |
+
# Set root logger to DEBUG level
|
13 |
+
logging.getLogger().setLevel(logging.DEBUG)
|
14 |
+
|
15 |
+
# Configure module logger
|
|
|
|
|
16 |
logger = logging.getLogger(__name__)
|
17 |
+
logger.setLevel(logging.DEBUG)
|
18 |
+
|
19 |
+
# Create handlers with DEBUG level
|
20 |
+
console_handler = logging.StreamHandler()
|
21 |
+
console_handler.setLevel(logging.DEBUG)
|
22 |
+
file_handler = logging.FileHandler('molecule_creation.log')
|
23 |
+
file_handler.setLevel(logging.DEBUG)
|
24 |
+
|
25 |
+
# Create and set formatter
|
26 |
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
27 |
+
console_handler.setFormatter(formatter)
|
28 |
+
file_handler.setFormatter(formatter)
|
29 |
+
|
30 |
+
# Add handlers to logger
|
31 |
+
logger.addHandler(console_handler)
|
32 |
+
logger.addHandler(file_handler)
|
33 |
+
|
34 |
+
# Atom display properties
|
35 |
+
ATOM_COLORS = {
|
36 |
+
'H': '#E8E8E8', # Light gray - more visible than pure white
|
37 |
+
'Be': '#42F5EC', # Bright turquoise - stands out nicely
|
38 |
+
'Li': '#FF34B3', # Hot pink - distinctive
|
39 |
+
'B': '#FFB347', # Pastel orange
|
40 |
+
'C': '#808080', # Classic gray for carbon
|
41 |
+
'N': '#4169E1', # Royal blue - richer than pure blue
|
42 |
+
'O': '#FF4500', # Orange red - more distinctive than pure red
|
43 |
+
'F': '#DAA520', # Goldenrod - warmer than yellow
|
44 |
+
'Na': '#9370DB', # Medium purple - softer than bright purple
|
45 |
+
'Mg': '#98FB98', # Pale green - distinctive from beryllium
|
46 |
+
'Al': '#DDA0DD' # Plum - distinctive pink/purple
|
47 |
+
}
|
48 |
|
49 |
+
# Bond display properties
|
50 |
+
BOND_STYLE = dict(
|
51 |
+
color='#2F4F4F', # Dark slate gray - better contrast
|
52 |
+
width=8 # Thicker bonds
|
53 |
+
)
|
54 |
+
|
55 |
+
ATOM_SIZES = {
|
56 |
+
'H': 12, # Smaller for hydrogen
|
57 |
+
'Be': 20, # Medium for beryllium
|
58 |
+
'Li': 20, # Medium for lithium
|
59 |
+
'B': 20, # Medium for boron
|
60 |
+
'C': 16, # Medium for carbon
|
61 |
+
'N': 16, # Medium for nitrogen
|
62 |
+
'O': 16, # Medium for oxygen
|
63 |
+
'F': 16, # Medium for fluorine
|
64 |
+
'Na': 24, # Larger for sodium
|
65 |
+
'Mg': 24, # Larger for magnesium
|
66 |
+
'Al': 20 # Medium for aluminum
|
67 |
+
}
|
68 |
|
69 |
def format_molecule_params(molecule_data: dict) -> str:
|
70 |
"""
|
|
|
123 |
# Handle both dictionary entries and direct energy values
|
124 |
for i, entry in enumerate(history):
|
125 |
if isinstance(entry, dict):
|
|
|
126 |
try:
|
127 |
iterations.append(entry.get('iteration', i))
|
128 |
energies.append(entry['energy'])
|
|
|
130 |
logger.warning(f"Skipping invalid entry {i}: {str(e)}")
|
131 |
continue
|
132 |
else:
|
|
|
133 |
try:
|
134 |
iterations.append(i)
|
135 |
energies.append(float(entry))
|
|
|
185 |
|
186 |
return fig
|
187 |
|
188 |
+
def create_molecule_viewer(molecule_id: str, scale_factor: float) -> go.Figure:
|
189 |
"""
|
190 |
+
Create a 3D visualization of the molecule using Plotly.
|
191 |
+
Uses geometry templates from molecules.json for accurate atomic positions.
|
|
|
|
|
192 |
|
193 |
Args:
|
194 |
molecule_id: Molecule identifier (e.g., "H2")
|
195 |
scale_factor: Bond length scaling factor (1.0 = original size)
|
196 |
+
Returns:
|
197 |
+
Plotly Figure object with 3D molecule visualization
|
198 |
"""
|
199 |
logger.info(f"Creating 3D view for {molecule_id} with scale_factor={scale_factor}")
|
200 |
|
|
|
208 |
return None
|
209 |
|
210 |
molecule_data = molecules[molecule_id]
|
211 |
+
if 'geometry_template' not in molecule_data:
|
212 |
+
logger.error(f"No geometry template found for {molecule_id}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
return None
|
214 |
|
215 |
+
geometry = molecule_data['geometry_template']
|
|
|
|
|
|
|
|
|
|
|
216 |
|
217 |
+
# Extract atomic positions and symbols
|
218 |
+
symbols = [atom[0] for atom in geometry]
|
219 |
+
positions = np.array([atom[1] for atom in geometry]) * scale_factor
|
220 |
+
|
221 |
+
# Create figure
|
222 |
+
fig = go.Figure()
|
223 |
+
|
224 |
+
# Add atoms as spheres
|
225 |
+
for symbol, pos in zip(symbols, positions):
|
226 |
+
color = ATOM_COLORS.get(symbol, '#808080') # Default gray if not specified
|
227 |
+
size = ATOM_SIZES.get(symbol, 20) # Default size if not specified
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
+
fig.add_trace(go.Scatter3d(
|
230 |
+
x=[pos[0]],
|
231 |
+
y=[pos[1]],
|
232 |
+
z=[pos[2]],
|
233 |
+
mode='markers+text',
|
234 |
+
marker=dict(
|
235 |
+
size=size,
|
236 |
+
color=color,
|
237 |
+
symbol='circle',
|
238 |
+
line=dict(color='black', width=1)
|
239 |
+
),
|
240 |
+
text=[symbol],
|
241 |
+
textposition='middle center',
|
242 |
+
name=symbol,
|
243 |
+
hoverinfo='name',
|
244 |
+
showlegend=False
|
245 |
+
))
|
246 |
|
247 |
+
# Add bonds between atoms
|
248 |
+
for i in range(len(symbols)):
|
249 |
+
for j in range(i + 1, len(symbols)):
|
250 |
+
# Calculate distance between atoms
|
251 |
+
dist = np.linalg.norm(positions[i] - positions[j])
|
252 |
+
# Add bond if atoms are close enough (adjust threshold as needed)
|
253 |
+
if dist < 2.5 * scale_factor: # Typical bond length is ~1-1.5 Å
|
254 |
+
# Create line between atoms
|
255 |
+
fig.add_trace(go.Scatter3d(
|
256 |
+
x=[positions[i][0], positions[j][0]],
|
257 |
+
y=[positions[i][1], positions[j][1]],
|
258 |
+
z=[positions[i][2], positions[j][2]],
|
259 |
+
mode='lines',
|
260 |
+
line=BOND_STYLE,
|
261 |
+
hoverinfo='none',
|
262 |
+
showlegend=False
|
263 |
+
))
|
264 |
+
|
265 |
+
# Update layout for better visualization
|
266 |
+
fig.update_layout(
|
267 |
+
scene=dict(
|
268 |
+
aspectmode='data', # Preserve actual molecular geometry
|
269 |
+
xaxis=dict(showspikes=False, showbackground=False, showticklabels=False, title=''),
|
270 |
+
yaxis=dict(showspikes=False, showbackground=False, showticklabels=False, title=''),
|
271 |
+
zaxis=dict(showspikes=False, showbackground=False, showticklabels=False, title=''),
|
272 |
+
camera=dict(
|
273 |
+
up=dict(x=0, y=1, z=0),
|
274 |
+
center=dict(x=0, y=0, z=0),
|
275 |
+
eye=dict(x=1.5, y=1.5, z=1.5)
|
276 |
+
)
|
277 |
+
),
|
278 |
+
margin=dict(l=0, r=0, t=0, b=0),
|
279 |
+
showlegend=False,
|
280 |
+
width=400,
|
281 |
+
height=400,
|
282 |
+
paper_bgcolor='rgba(0,0,0,0)',
|
283 |
+
plot_bgcolor='rgba(0,0,0,0)'
|
284 |
+
)
|
285 |
+
|
286 |
+
return fig
|
287 |
+
|
288 |
except Exception as e:
|
289 |
logger.error(f"Failed to create molecule viewer: {e}", exc_info=True)
|
290 |
return None
|