annabossler commited on
Commit
0a04e5a
·
verified ·
1 Parent(s): d7d9940

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -48
app.py CHANGED
@@ -7,25 +7,64 @@ from ase.io import read
7
  from ase.io.trajectory import Trajectory
8
  import hashlib
9
 
10
- # ==== Intentar visor nativo como en UMA (opcional) ====
11
  try:
12
  from gradio_molecule3d import Molecule3D
13
- HAVE_MOL3D = False # Forzar HTML siempre
14
- except Exception:
 
15
  HAVE_MOL3D = False
 
16
 
17
- # ==== Fallback HTML con 3Dmol.js ====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def traj_to_html(traj_path, width=520, height=520, interval_ms=200):
19
  """
20
- Render de una trayectoria ASE (.traj) con 3Dmol.js (sin depender de Jupyter).
21
  """
22
- # ID único basado en el path
23
- viewer_id = f"viewer_{abs(hash(traj_path))}"
 
 
24
 
25
  try:
26
  traj = Trajectory(traj_path)
 
 
27
  except Exception as e:
28
- return f"<div style='color:#b00'>Error leyendo trayectoria: {e}</div>"
29
 
30
  xyz_frames = []
31
  for atoms in traj:
@@ -36,37 +75,27 @@ def traj_to_html(traj_path, width=520, height=520, interval_ms=200):
36
  parts.append(f"{s} {x:.6f} {y:.6f} {z:.6f}")
37
  xyz_frames.append("\n".join(parts))
38
 
39
- if not xyz_frames:
40
- return "<div style='color:#555'>Empty trajectory</div>"
41
-
42
  frames_json = str(xyz_frames).replace("'", '"')
43
 
44
  html = f"""
45
- <div style="margin-bottom:10px;">
46
- <strong>3D Molecular Viewer</strong> - {len(xyz_frames)} frames
47
  </div>
48
- <div id="{viewer_id}" style="width:{width}px; height:{height}px; position:relative; border:1px solid #ccc; background:white;"></div>
49
  <script>
50
- // Load 3Dmol if not already loaded
51
  if (typeof window.$3Dmol === 'undefined') {{
52
  var script = document.createElement('script');
53
  script.src = 'https://3dmol.org/build/3Dmol-min.js';
54
  script.onload = function() {{
55
- setTimeout(function() {{ initViewer_{abs(hash(traj_path))}(); }}, 100);
56
  }};
57
  document.head.appendChild(script);
58
  }} else {{
59
- initViewer_{abs(hash(traj_path))}();
60
  }}
61
-
62
- function initViewer_{abs(hash(traj_path))}() {{
63
  var el = document.getElementById("{viewer_id}");
64
- if (!el) return;
65
-
66
- if (typeof $3Dmol === "undefined") {{
67
- el.innerHTML = '<div style="padding:20px;text-align:center;color:#666;">3Dmol.js no disponible</div>';
68
- return;
69
- }}
70
 
71
  var viewer = $3Dmol.createViewer(el, {{backgroundColor: 'white'}});
72
  var frames = {frames_json};
@@ -93,7 +122,7 @@ function initViewer_{abs(hash(traj_path))}() {{
93
  """
94
  return html
95
 
96
- # ==== OrbMol SPE directo (tu calculadora NO se toca) ====
97
  from orb_models.forcefield import pretrained
98
  from orb_models.forcefield.calculator import ORBCalculator
99
 
@@ -142,13 +171,12 @@ def predict_molecule(xyz_content, charge=0, spin_multiplicity=1):
142
  except Exception as e:
143
  return f"Error during calculation: {e}", "Error"
144
 
145
- # ==== Simulaciones (helpers locales, ya los tienes en simulation_scripts_orbmol.py) ====
146
  from simulation_scripts_orbmol import (
147
  run_md_simulation,
148
  run_relaxation_simulation,
149
  )
150
 
151
- # --- Utilidad: si el usuario pega XYZ en el textbox, guardamos a .xyz temporal ---
152
  def _string_looks_like_xyz(text: str) -> bool:
153
  try:
154
  first = (text or "").strip().splitlines()[0]
@@ -165,7 +193,7 @@ def _to_file_if_xyz(input_or_path: str):
165
  return tf.name, True
166
  return input_or_path, False
167
 
168
- # Wrappers: devuelven SIEMPRE (status, traj_path, log, script, explain, html_fallback)
169
  def md_wrapper(xyz_content, charge, spin, steps, tempK, timestep_fs, ensemble):
170
  tmp_created = False
171
  path_or_str = xyz_content
@@ -184,12 +212,16 @@ def md_wrapper(xyz_content, charge, spin, steps, tempK, timestep_fs, ensemble):
184
  )
185
  status = f"MD completed: {int(steps)} steps at {int(tempK)} K ({ensemble})"
186
 
187
- # SIEMPRE generar HTML ya que HAVE_MOL3D = False
188
- html_value = traj_to_html(traj_path)
189
- return (status, traj_path, log_text, script_text, explanation, html_value)
 
 
 
 
190
 
191
  except Exception as e:
192
- return (f"Error: {e}", None, "", "", "", "")
193
  finally:
194
  if tmp_created and isinstance(path_or_str, str) and os.path.exists(path_or_str):
195
  try: os.remove(path_or_str)
@@ -211,18 +243,22 @@ def relax_wrapper(xyz_content, steps, fmax, charge, spin, relax_cell):
211
  )
212
  status = f"Relaxation finished (≤ {int(steps)} steps, fmax={float(fmax)} eV/Å)"
213
 
214
- # SIEMPRE generar HTML ya que HAVE_MOL3D = False
215
- html_value = traj_to_html(traj_path)
216
- return (status, traj_path, log_text, script_text, explanation, html_value)
 
 
 
 
217
 
218
  except Exception as e:
219
- return (f"Error: {e}", None, "", "", "", "")
220
  finally:
221
  if tmp_created and isinstance(path_or_str, str) and os.path.exists(path_or_str):
222
  try: os.remove(path_or_str)
223
  except Exception: pass
224
 
225
- # ==== Ejemplos ====
226
  examples = [
227
  ["""2
228
  Hydrogen molecule
@@ -242,10 +278,10 @@ H -0.3630 -0.5133 0.8887
242
  H -0.3630 -0.5133 -0.8887""", 0, 1],
243
  ]
244
 
245
- # ==== UI ====
246
  with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
247
  with gr.Tabs():
248
- # -------- SPE --------
249
  with gr.Tab("Single Point Energy"):
250
  with gr.Row():
251
  with gr.Column(scale=2):
@@ -263,7 +299,7 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
263
  gr.Examples(examples=examples, inputs=[xyz_input, charge_input, spin_input])
264
  run_spe.click(predict_molecule, [xyz_input, charge_input, spin_input], [spe_out, spe_status])
265
 
266
- # -------- MD --------
267
  with gr.Tab("Molecular Dynamics"):
268
  with gr.Row():
269
  with gr.Column(scale=2):
@@ -283,8 +319,13 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
283
  md_status = gr.Textbox(label="MD Status", interactive=False)
284
  md_traj = gr.File(label="Trajectory (.traj)", interactive=False)
285
 
286
- # Siempre usar HTML ya que HAVE_MOL3D = False
287
- md_html = gr.HTML(label="Trajectory Viewer")
 
 
 
 
 
288
 
289
  md_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
290
  md_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=20, max_lines=30)
@@ -293,10 +334,10 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
293
  run_md_btn.click(
294
  md_wrapper,
295
  inputs=[xyz_md, charge_md, spin_md, steps_md, temp_md, timestep_md, ensemble_md],
296
- outputs=[md_status, md_traj, md_log, md_script, md_explain, md_html],
297
  )
298
 
299
- # -------- Relax --------
300
  with gr.Tab("Relaxation / Optimization"):
301
  with gr.Row():
302
  with gr.Column(scale=2):
@@ -313,8 +354,13 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
313
  rlx_status = gr.Textbox(label="Status", interactive=False)
314
  rlx_traj = gr.File(label="Trajectory (.traj)", interactive=False)
315
 
316
- # Siempre usar HTML ya que HAVE_MOL3D = False
317
- rlx_html = gr.HTML(label="Final Structure")
 
 
 
 
 
318
 
319
  rlx_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
320
  rlx_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=20, max_lines=30)
@@ -323,7 +369,7 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
323
  run_rlx_btn.click(
324
  relax_wrapper,
325
  inputs=[xyz_rlx, steps_rlx, fmax_rlx, charge_rlx, spin_rlx, relax_cell],
326
- outputs=[rlx_status, rlx_traj, rlx_log, rlx_script, rlx_explain, rlx_html],
327
  )
328
 
329
  print("Starting OrbMol model loading…")
 
7
  from ase.io.trajectory import Trajectory
8
  import hashlib
9
 
10
+ # ==== Usar componente nativo Molecule3D ====
11
  try:
12
  from gradio_molecule3d import Molecule3D
13
+ HAVE_MOL3D = True # Ahora usar el componente nativo
14
+ print("✅ gradio_molecule3d loaded successfully")
15
+ except Exception as e:
16
  HAVE_MOL3D = False
17
+ print(f"❌ gradio_molecule3d not available: {e}")
18
 
19
+ # ==== Función para convertir trayectoria a archivo temporal XYZ ====
20
+ def traj_to_molecule3d_file(traj_path):
21
+ """
22
+ Convierte una trayectoria ASE (.traj) a un archivo XYZ temporal para Molecule3D.
23
+ Retorna el path del archivo temporal.
24
+ """
25
+ if not traj_path or not os.path.exists(traj_path):
26
+ return None
27
+
28
+ try:
29
+ traj = Trajectory(traj_path)
30
+ if len(traj) == 0:
31
+ return None
32
+
33
+ # Usar último frame para visualización estática
34
+ atoms = traj[-1]
35
+
36
+ # Crear archivo XYZ temporal
37
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
38
+ symbols = atoms.get_chemical_symbols()
39
+ coords = atoms.get_positions()
40
+
41
+ f.write(f"{len(symbols)}\n")
42
+ f.write("Generated by OrbMol\n")
43
+ for s, (x, y, z) in zip(symbols, coords):
44
+ f.write(f"{s} {x:.6f} {y:.6f} {z:.6f}\n")
45
+
46
+ return f.name
47
+
48
+ except Exception as e:
49
+ print(f"Error converting trajectory: {e}")
50
+ return None
51
+
52
+ # ==== Fallback HTML con 3Dmol.js (por si acaso) ====
53
  def traj_to_html(traj_path, width=520, height=520, interval_ms=200):
54
  """
55
+ Fallback HTML viewer (solo si Molecule3D no está disponible).
56
  """
57
+ if not traj_path or not os.path.exists(traj_path):
58
+ return "<div style='color:#b00; padding:20px;'>No trajectory file found</div>"
59
+
60
+ viewer_id = f"viewer_{abs(hash(traj_path)) % 10000}"
61
 
62
  try:
63
  traj = Trajectory(traj_path)
64
+ if len(traj) == 0:
65
+ return "<div style='color:#555; padding:20px;'>Empty trajectory</div>"
66
  except Exception as e:
67
+ return f"<div style='color:#b00; padding:20px;'>Error: {e}</div>"
68
 
69
  xyz_frames = []
70
  for atoms in traj:
 
75
  parts.append(f"{s} {x:.6f} {y:.6f} {z:.6f}")
76
  xyz_frames.append("\n".join(parts))
77
 
 
 
 
78
  frames_json = str(xyz_frames).replace("'", '"')
79
 
80
  html = f"""
81
+ <div style="margin-bottom:10px; padding:10px; background:#f5f5f5; border-radius:5px;">
82
+ <strong>🧬 3D Molecular Viewer</strong> - {len(xyz_frames)} frames
83
  </div>
84
+ <div id="{viewer_id}" style="width:{width}px; height:{height}px; position:relative; border:2px solid #ddd; border-radius:8px; background:#fafafa;"></div>
85
  <script>
 
86
  if (typeof window.$3Dmol === 'undefined') {{
87
  var script = document.createElement('script');
88
  script.src = 'https://3dmol.org/build/3Dmol-min.js';
89
  script.onload = function() {{
90
+ setTimeout(function() {{ initViewer_{viewer_id}(); }}, 100);
91
  }};
92
  document.head.appendChild(script);
93
  }} else {{
94
+ initViewer_{viewer_id}();
95
  }}
96
+ function initViewer_{viewer_id}() {{
 
97
  var el = document.getElementById("{viewer_id}");
98
+ if (!el || typeof $3Dmol === "undefined") return;
 
 
 
 
 
99
 
100
  var viewer = $3Dmol.createViewer(el, {{backgroundColor: 'white'}});
101
  var frames = {frames_json};
 
122
  """
123
  return html
124
 
125
+ # ==== OrbMol SPE directo (sin cambios) ====
126
  from orb_models.forcefield import pretrained
127
  from orb_models.forcefield.calculator import ORBCalculator
128
 
 
171
  except Exception as e:
172
  return f"Error during calculation: {e}", "Error"
173
 
174
+ # ==== Simulaciones (sin cambios) ====
175
  from simulation_scripts_orbmol import (
176
  run_md_simulation,
177
  run_relaxation_simulation,
178
  )
179
 
 
180
  def _string_looks_like_xyz(text: str) -> bool:
181
  try:
182
  first = (text or "").strip().splitlines()[0]
 
193
  return tf.name, True
194
  return input_or_path, False
195
 
196
+ # Wrappers actualizados para devolver archivos para Molecule3D
197
  def md_wrapper(xyz_content, charge, spin, steps, tempK, timestep_fs, ensemble):
198
  tmp_created = False
199
  path_or_str = xyz_content
 
212
  )
213
  status = f"MD completed: {int(steps)} steps at {int(tempK)} K ({ensemble})"
214
 
215
+ # Usar Molecule3D si está disponible, sino HTML
216
+ if HAVE_MOL3D:
217
+ xyz_file = traj_to_molecule3d_file(traj_path)
218
+ return (status, traj_path, log_text, script_text, explanation, xyz_file, "")
219
+ else:
220
+ html_value = traj_to_html(traj_path)
221
+ return (status, traj_path, log_text, script_text, explanation, None, html_value)
222
 
223
  except Exception as e:
224
+ return (f"Error: {e}", None, "", "", "", None, "")
225
  finally:
226
  if tmp_created and isinstance(path_or_str, str) and os.path.exists(path_or_str):
227
  try: os.remove(path_or_str)
 
243
  )
244
  status = f"Relaxation finished (≤ {int(steps)} steps, fmax={float(fmax)} eV/Å)"
245
 
246
+ # Usar Molecule3D si está disponible, sino HTML
247
+ if HAVE_MOL3D:
248
+ xyz_file = traj_to_molecule3d_file(traj_path)
249
+ return (status, traj_path, log_text, script_text, explanation, xyz_file, "")
250
+ else:
251
+ html_value = traj_to_html(traj_path)
252
+ return (status, traj_path, log_text, script_text, explanation, None, html_value)
253
 
254
  except Exception as e:
255
+ return (f"Error: {e}", None, "", "", "", None, "")
256
  finally:
257
  if tmp_created and isinstance(path_or_str, str) and os.path.exists(path_or_str):
258
  try: os.remove(path_or_str)
259
  except Exception: pass
260
 
261
+ # ==== Ejemplos (sin cambios) ====
262
  examples = [
263
  ["""2
264
  Hydrogen molecule
 
278
  H -0.3630 -0.5133 -0.8887""", 0, 1],
279
  ]
280
 
281
+ # ==== UI actualizada ====
282
  with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
283
  with gr.Tabs():
284
+ # -------- SPE (sin cambios) --------
285
  with gr.Tab("Single Point Energy"):
286
  with gr.Row():
287
  with gr.Column(scale=2):
 
299
  gr.Examples(examples=examples, inputs=[xyz_input, charge_input, spin_input])
300
  run_spe.click(predict_molecule, [xyz_input, charge_input, spin_input], [spe_out, spe_status])
301
 
302
+ # -------- MD (actualizada con Molecule3D) --------
303
  with gr.Tab("Molecular Dynamics"):
304
  with gr.Row():
305
  with gr.Column(scale=2):
 
319
  md_status = gr.Textbox(label="MD Status", interactive=False)
320
  md_traj = gr.File(label="Trajectory (.traj)", interactive=False)
321
 
322
+ # Usar Molecule3D si está disponible
323
+ if HAVE_MOL3D:
324
+ md_viewer = Molecule3D(label="3D Molecular Viewer")
325
+ md_html = gr.HTML(visible=False) # Oculto cuando usamos Molecule3D
326
+ else:
327
+ md_viewer = gr.HTML(visible=False) # Placeholder
328
+ md_html = gr.HTML(label="Trajectory Viewer")
329
 
330
  md_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
331
  md_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=20, max_lines=30)
 
334
  run_md_btn.click(
335
  md_wrapper,
336
  inputs=[xyz_md, charge_md, spin_md, steps_md, temp_md, timestep_md, ensemble_md],
337
+ outputs=[md_status, md_traj, md_log, md_script, md_explain, md_viewer, md_html],
338
  )
339
 
340
+ # -------- Relax (actualizada con Molecule3D) --------
341
  with gr.Tab("Relaxation / Optimization"):
342
  with gr.Row():
343
  with gr.Column(scale=2):
 
354
  rlx_status = gr.Textbox(label="Status", interactive=False)
355
  rlx_traj = gr.File(label="Trajectory (.traj)", interactive=False)
356
 
357
+ # Usar Molecule3D si está disponible
358
+ if HAVE_MOL3D:
359
+ rlx_viewer = Molecule3D(label="Final Structure")
360
+ rlx_html = gr.HTML(visible=False) # Oculto cuando usamos Molecule3D
361
+ else:
362
+ rlx_viewer = gr.HTML(visible=False) # Placeholder
363
+ rlx_html = gr.HTML(label="Final Structure")
364
 
365
  rlx_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
366
  rlx_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=20, max_lines=30)
 
369
  run_rlx_btn.click(
370
  relax_wrapper,
371
  inputs=[xyz_rlx, steps_rlx, fmax_rlx, charge_rlx, spin_rlx, relax_cell],
372
+ outputs=[rlx_status, rlx_traj, rlx_log, rlx_script, rlx_explain, rlx_viewer, rlx_html],
373
  )
374
 
375
  print("Starting OrbMol model loading…")