annabossler commited on
Commit
1fedfc5
·
verified ·
1 Parent(s): 6036c31

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -122
app.py CHANGED
@@ -1,72 +1,82 @@
1
  import os
2
- import re
3
  import numpy as np
4
  import gradio as gr
5
- from ase.io import read
6
  from ase.io.trajectory import Trajectory
 
7
 
8
- # ========= 3Dmol.js (para ver trayectorias MD/Relax) =========
9
- THREE_D_MOL_SOURCES = [
10
- "https://3dmol.org/build/3Dmol-min.js",
11
- "https://cdn.jsdelivr.net/npm/3dmol/build/3Dmol-min.js",
12
- "https://unpkg.com/3dmol/build/3Dmol-min.js",
13
- ]
14
-
15
- def _loader_js():
16
- srcs = "[" + ",".join([f"'{u}'" for u in THREE_D_MOL_SOURCES]) + "]"
17
- return f"""
18
- function _ensure3Dmol(cb){{
19
- if(typeof window.$3Dmol!=='undefined') return cb();
20
- const srcs={srcs}; let i=0;
21
- function tryNext(){{
22
- if(i>=srcs.length) return;
23
- const s=document.createElement('script'); s.src=srcs[i++]; s.onload=cb; s.onerror=tryNext; document.head.appendChild(s);
24
- }}
25
- tryNext();
26
- }}
27
- """
28
-
29
- def _atoms_to_xyz_block(atoms):
30
- syms = atoms.get_chemical_symbols()
31
- pos = atoms.get_positions()
32
- out = [str(len(syms)), "frame"]
33
- for s,(x,y,z) in zip(syms,pos):
34
- out.append(f"{s} {x:.6f} {y:.6f} {z:.6f}")
35
- return "\n".join(out)
36
 
 
37
  def traj_to_html(traj_path, width=520, height=520, interval_ms=200):
 
 
 
38
  if not traj_path or not os.path.exists(traj_path):
39
- return "<div style='color:#b00;padding:20px;'>No trajectory file found</div>"
 
 
 
40
  try:
41
  traj = Trajectory(traj_path)
42
- if len(traj)==0:
43
- return "<div style='color:#555;padding:20px;'>Empty trajectory</div>"
44
  except Exception as e:
45
- return f"<div style='color:#b00;padding:20px;'>Error reading trajectory: {e}</div>"
46
-
47
- frames = [_atoms_to_xyz_block(at) for at in traj]
48
- frames_json = str(frames).replace("'", '"')
49
- viewer_id = f"viewer_{abs(hash(traj_path))%100000}"
50
- loader = _loader_js()
51
- return f"""
52
- <div style="margin-bottom:10px;padding:10px;background:#f5f5f5;border-radius:5px;">
53
- <strong>🧬 3D Molecular Viewer</strong> — {len(frames)} frames
 
 
 
 
 
 
 
54
  </div>
55
- <div id="{viewer_id}" style="width:{width}px;height:{height}px;border:2px solid #ddd;border-radius:8px;background:#fafafa;"></div>
56
  <script>
57
- {loader}
58
- _ensure3Dmol(function(){{
59
- var el=document.getElementById("{viewer_id}"); if(!el||typeof $3Dmol==='undefined') return;
60
- var v=$3Dmol.createViewer(el, {{backgroundColor:'white'}});
61
- var frames={frames_json}; var i=0;
62
- function draw(k){{ v.clear(); v.addModel(frames[k], "xyz"); v.setStyle({{}}, {{stick:{{}}, sphere:{{}}}}); v.zoomTo(); v.render(); }}
63
- draw(0);
64
- if(frames.length>1) setInterval(function(){{ i=(i+1)%frames.length; draw(i); }}, {interval_ms});
65
- }});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  </script>
67
  """
 
68
 
69
- # ================= OrbMol (SPE) =================
70
  from orb_models.forcefield import pretrained
71
  from orb_models.forcefield.calculator import ORBCalculator
72
 
@@ -74,22 +84,28 @@ _MODEL_CALC = None
74
  def _load_orbmol_calc():
75
  global _MODEL_CALC
76
  if _MODEL_CALC is None:
77
- orbff = pretrained.orb_v3_conservative_inf_omat(device="cpu", precision="float32-high")
 
 
78
  _MODEL_CALC = ORBCalculator(orbff, device="cpu")
79
  return _MODEL_CALC
80
 
81
  def predict_molecule(structure_file, charge=0, spin_multiplicity=1):
82
  """
83
- Single Point Energy + fuerzas (OrbMol). Solo se ejecuta al pulsar el botón.
84
  """
85
  try:
86
  calc = _load_orbmol_calc()
87
  if not structure_file:
88
  return "Error: Please upload a structure file", "Error"
89
 
90
- file_path = structure_file # gr.File(type='filepath') -> str
 
 
 
91
  if not os.path.exists(file_path):
92
  return f"Error: File not found: {file_path}", "Error"
 
93
  if os.path.getsize(file_path) == 0:
94
  return f"Error: Empty file: {file_path}", "Error"
95
 
@@ -110,25 +126,20 @@ def predict_molecule(structure_file, charge=0, spin_multiplicity=1):
110
  except Exception as e:
111
  return f"Error during calculation: {e}", "Error"
112
 
113
- # ================= Simulaciones (tus helpers) =================
114
  from simulation_scripts_orbmol import (
115
  run_md_simulation,
116
  run_relaxation_simulation,
117
  )
118
 
119
- # ========== Wrappers MD / Relax (con firma correcta) ==========
120
  def md_wrapper(structure_file, charge, spin, steps, tempK, timestep_fs, ensemble):
121
- """
122
- Llama a tu run_md_simulation con keywords correctos.
123
- """
124
  try:
125
  if not structure_file:
126
- return ("Error: Please upload a structure file", None, "", "", "", "", None)
 
 
127
  file_path = structure_file
128
- if not os.path.exists(file_path):
129
- return ("Error: File not found: " + str(file_path), None, "", "", "", "", None)
130
- if os.path.getsize(file_path) == 0:
131
- return ("Error: Empty file: " + str(file_path), None, "", "", "", "", None)
132
 
133
  traj_path, log_text, script_text, explanation = run_md_simulation(
134
  file_path,
@@ -137,134 +148,145 @@ def md_wrapper(structure_file, charge, spin, steps, tempK, timestep_fs, ensemble
137
  float(timestep_fs),
138
  float(tempK),
139
  "NVT" if ensemble == "NVT" else "NVE",
140
- total_charge=int(charge),
141
- spin_multiplicity=int(spin),
142
  )
143
  status = f"MD completed: {int(steps)} steps at {int(tempK)} K ({ensemble})"
 
144
  html_value = traj_to_html(traj_path)
145
- return (status, traj_path, log_text, script_text, explanation, html_value, None)
 
146
  except Exception as e:
147
- return (f"Error: {e}", None, "", "", "", "", None)
148
 
149
  def relax_wrapper(structure_file, steps, fmax, charge, spin, relax_cell):
150
- """
151
- Llama a run_relaxation_simulation con keywords correctos.
152
- """
153
  try:
154
  if not structure_file:
155
- return ("Error: Please upload a structure file", None, "", "", "", "", None)
 
 
156
  file_path = structure_file
157
- if not os.path.exists(file_path):
158
- return ("Error: File not found: " + str(file_path), None, "", "", "", "", None)
159
- if os.path.getsize(file_path) == 0:
160
- return ("Error: Empty file: " + str(file_path), None, "", "", "", "", None)
161
 
162
  traj_path, log_text, script_text, explanation = run_relaxation_simulation(
163
  file_path,
164
  int(steps),
165
  float(fmax),
166
- total_charge=int(charge),
167
- spin_multiplicity=int(spin),
168
- relax_unit_cell=bool(relax_cell),
169
  )
170
  status = f"Relaxation finished (≤ {int(steps)} steps, fmax={float(fmax)} eV/Å)"
 
171
  html_value = traj_to_html(traj_path)
172
- return (status, traj_path, log_text, script_text, explanation, html_value, None)
 
173
  except Exception as e:
174
- return (f"Error: {e}", None, "", "", "", "", None)
175
 
176
- # ===================== UI (solo calcula al pulsar botón) =====================
177
  with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
178
  with gr.Tabs():
179
- # ===== SPE =====
180
  with gr.Tab("Single Point Energy"):
181
  with gr.Row():
182
  with gr.Column(scale=2):
183
- gr.Markdown("## OrbMol — Single Point Energy")
 
 
184
  xyz_input = gr.File(
185
- label="Upload Structure File (.xyz/.pdb/.cif/.traj/.mol/.sdf)",
186
  file_types=[".xyz", ".pdb", ".cif", ".traj", ".mol", ".sdf"],
187
- file_count="single",
188
- type="filepath",
189
  )
 
190
  with gr.Row():
191
  charge_input = gr.Slider(minimum=-10, maximum=10, value=0, step=1, label="Charge")
192
- spin_input = gr.Slider(minimum=1, maximum=11, value=1, step=1, label="Spin Multiplicity")
193
  run_spe = gr.Button("Run OrbMol Prediction", variant="primary")
194
- with gr.Column(variant="panel", min_width=520):
195
- spe_out = gr.Textbox(label="Energy & Forces", lines=18, interactive=False)
 
196
  spe_status = gr.Textbox(label="Status", interactive=False, max_lines=1)
 
197
  run_spe.click(predict_molecule, [xyz_input, charge_input, spin_input], [spe_out, spe_status])
198
 
199
- # ===== MD =====
200
  with gr.Tab("Molecular Dynamics"):
201
  with gr.Row():
202
  with gr.Column(scale=2):
203
  gr.Markdown("## Molecular Dynamics Simulation")
 
 
204
  xyz_md = gr.File(
205
- label="Upload Structure File (.xyz/.pdb/.cif/.traj/.mol/.sdf)",
206
  file_types=[".xyz", ".pdb", ".cif", ".traj", ".mol", ".sdf"],
207
- file_count="single",
208
- type="filepath",
209
  )
 
210
  with gr.Row():
211
  charge_md = gr.Slider(minimum=-10, maximum=10, value=0, step=1, label="Charge")
212
- spin_md = gr.Slider(minimum=1, maximum=11, value=1, step=1, label="Spin Multiplicity")
213
  with gr.Row():
214
- steps_md = gr.Slider(minimum=10, maximum=2000, value=100, step=10, label="Steps")
215
- temp_md = gr.Slider(minimum=10, maximum=1500, value=300, step=10, label="Temperature (K)")
216
  with gr.Row():
217
  timestep_md = gr.Slider(minimum=0.1, maximum=5.0, value=1.0, step=0.1, label="Timestep (fs)")
218
- ensemble_md = gr.Radio(["NVE","NVT"], value="NVE", label="Ensemble")
219
  run_md_btn = gr.Button("Run MD Simulation", variant="primary")
 
220
  with gr.Column(variant="panel", min_width=520):
221
  md_status = gr.Textbox(label="MD Status", interactive=False)
222
- md_traj = gr.File(label="Trajectory (.traj)", interactive=False)
223
- md_html = gr.HTML(label="Trajectory Viewer") # <- sin sanitize
224
- md_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
225
- md_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=18, max_lines=28)
226
- md_explain= gr.Markdown()
227
- md_plot = gr.Plot(label="(optional)")
 
228
  run_md_btn.click(
229
  md_wrapper,
230
  inputs=[xyz_md, charge_md, spin_md, steps_md, temp_md, timestep_md, ensemble_md],
231
- outputs=[md_status, md_traj, md_log, md_script, md_explain, md_html, md_plot],
232
  )
233
 
234
- # ===== Relax =====
235
  with gr.Tab("Relaxation / Optimization"):
236
  with gr.Row():
237
  with gr.Column(scale=2):
238
  gr.Markdown("## Structure Relaxation/Optimization")
 
 
239
  xyz_rlx = gr.File(
240
- label="Upload Structure File (.xyz/.pdb/.cif/.traj/.mol/.sdf)",
241
  file_types=[".xyz", ".pdb", ".cif", ".traj", ".mol", ".sdf"],
242
- file_count="single",
243
- type="filepath",
244
  )
 
245
  steps_rlx = gr.Slider(minimum=1, maximum=2000, value=300, step=1, label="Max Steps")
246
- fmax_rlx = gr.Slider(minimum=0.001, maximum=0.5, value=0.05, step=0.001, label="Fmax (eV/Å)")
247
  with gr.Row():
248
  charge_rlx = gr.Slider(minimum=-10, maximum=10, value=0, step=1, label="Charge")
249
- spin_rlx = gr.Slider(minimum=1, maximum=11, value=1, step=1, label="Spin")
250
  relax_cell = gr.Checkbox(False, label="Relax Unit Cell")
251
- run_rlx_btn= gr.Button("Run Optimization", variant="primary")
 
252
  with gr.Column(variant="panel", min_width=520):
253
  rlx_status = gr.Textbox(label="Status", interactive=False)
254
- rlx_traj = gr.File(label="Trajectory (.traj)", interactive=False)
255
- rlx_html = gr.HTML(label="Final Structure / Trajectory") # <- sin sanitize
256
- rlx_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
257
- rlx_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=18, max_lines=28)
258
- rlx_explain= gr.Markdown()
259
- rlx_plot = gr.Plot(label="(optional)")
 
260
  run_rlx_btn.click(
261
  relax_wrapper,
262
  inputs=[xyz_rlx, steps_rlx, fmax_rlx, charge_rlx, spin_rlx, relax_cell],
263
- outputs=[rlx_status, rlx_traj, rlx_log, rlx_script, rlx_explain, rlx_html, rlx_plot],
264
  )
265
 
266
  print("Starting OrbMol model loading…")
267
  _ = _load_orbmol_calc()
268
 
269
  if __name__ == "__main__":
270
- demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
 
1
  import os
2
+ import tempfile
3
  import numpy as np
4
  import gradio as gr
5
+ from ase.io import read, write
6
  from ase.io.trajectory import Trajectory
7
+ import hashlib
8
 
9
+ # ==== Forzar visor HTML con 3Dmol.js ====
10
+ HAVE_MOL3D = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ # ==== Fallback HTML con 3Dmol.js ====
13
  def traj_to_html(traj_path, width=520, height=520, interval_ms=200):
14
+ """
15
+ Lee una .traj de ASE y genera un visor HTML (3Dmol.js) con animación.
16
+ """
17
  if not traj_path or not os.path.exists(traj_path):
18
+ return "<div style='color:#b00; padding:20px;'>No trajectory file found</div>"
19
+
20
+ viewer_id = f"viewer_{abs(hash(traj_path)) % 100000}"
21
+
22
  try:
23
  traj = Trajectory(traj_path)
24
+ if len(traj) == 0:
25
+ return "<div style='color:#555; padding:20px;'>Empty trajectory</div>"
26
  except Exception as e:
27
+ return f"<div style='color:#b00; padding:20px;'>Error: {e}</div>"
28
+
29
+ xyz_frames = []
30
+ for atoms in traj:
31
+ symbols = atoms.get_chemical_symbols()
32
+ coords = atoms.get_positions()
33
+ parts = [str(len(symbols)), "frame"]
34
+ for s, (x, y, z) in zip(symbols, coords):
35
+ parts.append(f"{s} {x:.6f} {y:.6f} {z:.6f}")
36
+ xyz_frames.append("\n".join(parts))
37
+
38
+ frames_json = str(xyz_frames).replace("'", '"')
39
+
40
+ html = f"""
41
+ <div style="margin-bottom:10px; padding:10px; background:#f5f5f5; border-radius:5px;">
42
+ <strong>🧬 3D Molecular Viewer</strong> — {len(xyz_frames)} frames
43
  </div>
44
+ <div id="{viewer_id}" style="width:{width}px; height:{height}px; position:relative; border:2px solid #ddd; border-radius:8px; background:#fafafa;"></div>
45
  <script>
46
+ if (typeof window.$3Dmol === 'undefined') {{
47
+ var script = document.createElement('script');
48
+ script.src = 'https://3dmol.org/build/3Dmol-min.js';
49
+ script.onload = function() {{ setTimeout(function() {{ initViewer_{viewer_id}(); }}, 100); }};
50
+ document.head.appendChild(script);
51
+ }} else {{
52
+ initViewer_{viewer_id}();
53
+ }}
54
+ function initViewer_{viewer_id}() {{
55
+ var el = document.getElementById("{viewer_id}");
56
+ if (!el || typeof $3Dmol === "undefined") return;
57
+ var viewer = $3Dmol.createViewer(el, {{backgroundColor: 'white'}});
58
+ var frames = {frames_json};
59
+ var currentFrame = 0;
60
+ function showFrame(i) {{
61
+ viewer.clear();
62
+ viewer.addModel(frames[i], "xyz");
63
+ viewer.setStyle({{}}, {{stick: {{}}, sphere: {{}}}});
64
+ viewer.zoomTo();
65
+ viewer.render();
66
+ }}
67
+ showFrame(0);
68
+ if (frames.length > 1) {{
69
+ setInterval(function() {{
70
+ currentFrame = (currentFrame + 1) % frames.length;
71
+ showFrame(currentFrame);
72
+ }}, {interval_ms});
73
+ }}
74
+ }}
75
  </script>
76
  """
77
+ return html
78
 
79
+ # ==== OrbMol SPE ====
80
  from orb_models.forcefield import pretrained
81
  from orb_models.forcefield.calculator import ORBCalculator
82
 
 
84
  def _load_orbmol_calc():
85
  global _MODEL_CALC
86
  if _MODEL_CALC is None:
87
+ orbff = pretrained.orb_v3_conservative_inf_omat(
88
+ device="cpu", precision="float32-high"
89
+ )
90
  _MODEL_CALC = ORBCalculator(orbff, device="cpu")
91
  return _MODEL_CALC
92
 
93
  def predict_molecule(structure_file, charge=0, spin_multiplicity=1):
94
  """
95
+ Single Point Energy + fuerzas (OrbMol). Acepta archivos subidos.
96
  """
97
  try:
98
  calc = _load_orbmol_calc()
99
  if not structure_file:
100
  return "Error: Please upload a structure file", "Error"
101
 
102
+ # structure_file es directamente el path del archivo en Gradio
103
+ file_path = structure_file
104
+
105
+ # Verificar que el archivo existe y no está vacío
106
  if not os.path.exists(file_path):
107
  return f"Error: File not found: {file_path}", "Error"
108
+
109
  if os.path.getsize(file_path) == 0:
110
  return f"Error: Empty file: {file_path}", "Error"
111
 
 
126
  except Exception as e:
127
  return f"Error during calculation: {e}", "Error"
128
 
129
+ # ==== Simulaciones (helpers) ====
130
  from simulation_scripts_orbmol import (
131
  run_md_simulation,
132
  run_relaxation_simulation,
133
  )
134
 
135
+ # ==== Wrappers: usan archivos subidos ====
136
  def md_wrapper(structure_file, charge, spin, steps, tempK, timestep_fs, ensemble):
 
 
 
137
  try:
138
  if not structure_file:
139
+ return ("Error: Please upload a structure file", None, "", "", "", None, "")
140
+
141
+ # structure_file es directamente el path del archivo
142
  file_path = structure_file
 
 
 
 
143
 
144
  traj_path, log_text, script_text, explanation = run_md_simulation(
145
  file_path,
 
148
  float(timestep_fs),
149
  float(tempK),
150
  "NVT" if ensemble == "NVT" else "NVE",
151
+ int(charge),
152
+ int(spin),
153
  )
154
  status = f"MD completed: {int(steps)} steps at {int(tempK)} K ({ensemble})"
155
+
156
  html_value = traj_to_html(traj_path)
157
+ return (status, traj_path, log_text, script_text, explanation, None, html_value)
158
+
159
  except Exception as e:
160
+ return (f"Error: {e}", None, "", "", "", None, "")
161
 
162
  def relax_wrapper(structure_file, steps, fmax, charge, spin, relax_cell):
 
 
 
163
  try:
164
  if not structure_file:
165
+ return ("Error: Please upload a structure file", None, "", "", "", None, "")
166
+
167
+ # structure_file es directamente el path del archivo
168
  file_path = structure_file
 
 
 
 
169
 
170
  traj_path, log_text, script_text, explanation = run_relaxation_simulation(
171
  file_path,
172
  int(steps),
173
  float(fmax),
174
+ int(charge),
175
+ int(spin),
176
+ bool(relax_cell),
177
  )
178
  status = f"Relaxation finished (≤ {int(steps)} steps, fmax={float(fmax)} eV/Å)"
179
+
180
  html_value = traj_to_html(traj_path)
181
+ return (status, traj_path, log_text, script_text, explanation, None, html_value)
182
+
183
  except Exception as e:
184
+ return (f"Error: {e}", None, "", "", "", None, "")
185
 
186
+ # ==== UI ====
187
  with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
188
  with gr.Tabs():
189
+ # -------- SPE --------
190
  with gr.Tab("Single Point Energy"):
191
  with gr.Row():
192
  with gr.Column(scale=2):
193
+ gr.Markdown("# OrbMol — Quantum-Accurate Molecular Predictions")
194
+ gr.Markdown("Upload molecular structure files (.xyz, .pdb, .cif, .traj) for energy and force calculations.")
195
+
196
  xyz_input = gr.File(
197
+ label="Upload Structure File (.xyz/.pdb/.cif/.traj)",
198
  file_types=[".xyz", ".pdb", ".cif", ".traj", ".mol", ".sdf"],
199
+ file_count="single"
 
200
  )
201
+
202
  with gr.Row():
203
  charge_input = gr.Slider(minimum=-10, maximum=10, value=0, step=1, label="Charge")
204
+ spin_input = gr.Slider(minimum=1, maximum=11, value=1, step=1, label="Spin Multiplicity")
205
  run_spe = gr.Button("Run OrbMol Prediction", variant="primary")
206
+
207
+ with gr.Column(variant="panel", min_width=500):
208
+ spe_out = gr.Textbox(label="Energy & Forces", lines=15, interactive=False)
209
  spe_status = gr.Textbox(label="Status", interactive=False, max_lines=1)
210
+
211
  run_spe.click(predict_molecule, [xyz_input, charge_input, spin_input], [spe_out, spe_status])
212
 
213
+ # -------- MD --------
214
  with gr.Tab("Molecular Dynamics"):
215
  with gr.Row():
216
  with gr.Column(scale=2):
217
  gr.Markdown("## Molecular Dynamics Simulation")
218
+ gr.Markdown("Upload your molecular structure and configure MD parameters.")
219
+
220
  xyz_md = gr.File(
221
+ label="Upload Structure File (.xyz/.pdb/.cif/.traj)",
222
  file_types=[".xyz", ".pdb", ".cif", ".traj", ".mol", ".sdf"],
223
+ file_count="single"
 
224
  )
225
+
226
  with gr.Row():
227
  charge_md = gr.Slider(minimum=-10, maximum=10, value=0, step=1, label="Charge")
228
+ spin_md = gr.Slider(minimum=1, maximum=11, value=1, step=1, label="Spin Multiplicity")
229
  with gr.Row():
230
+ steps_md = gr.Slider(minimum=10, maximum=2000, value=100, step=10, label="Steps")
231
+ temp_md = gr.Slider(minimum=10, maximum=1500, value=300, step=10, label="Temperature (K)")
232
  with gr.Row():
233
  timestep_md = gr.Slider(minimum=0.1, maximum=5.0, value=1.0, step=0.1, label="Timestep (fs)")
234
+ ensemble_md = gr.Radio(["NVE", "NVT"], value="NVE", label="Ensemble")
235
  run_md_btn = gr.Button("Run MD Simulation", variant="primary")
236
+
237
  with gr.Column(variant="panel", min_width=520):
238
  md_status = gr.Textbox(label="MD Status", interactive=False)
239
+ md_traj = gr.File(label="Trajectory (.traj)", interactive=False)
240
+ md_viewer_placeholder = gr.HTML(visible=False)
241
+ md_html = gr.HTML(label="Trajectory Viewer")
242
+ md_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
243
+ md_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=20, max_lines=30)
244
+ md_explain = gr.Markdown()
245
+
246
  run_md_btn.click(
247
  md_wrapper,
248
  inputs=[xyz_md, charge_md, spin_md, steps_md, temp_md, timestep_md, ensemble_md],
249
+ outputs=[md_status, md_traj, md_log, md_script, md_explain, md_viewer_placeholder, md_html],
250
  )
251
 
252
+ # -------- Relax --------
253
  with gr.Tab("Relaxation / Optimization"):
254
  with gr.Row():
255
  with gr.Column(scale=2):
256
  gr.Markdown("## Structure Relaxation/Optimization")
257
+ gr.Markdown("Upload your molecular structure for geometry optimization.")
258
+
259
  xyz_rlx = gr.File(
260
+ label="Upload Structure File (.xyz/.pdb/.cif/.traj)",
261
  file_types=[".xyz", ".pdb", ".cif", ".traj", ".mol", ".sdf"],
262
+ file_count="single"
 
263
  )
264
+
265
  steps_rlx = gr.Slider(minimum=1, maximum=2000, value=300, step=1, label="Max Steps")
266
+ fmax_rlx = gr.Slider(minimum=0.001, maximum=0.5, value=0.05, step=0.001, label="Fmax (eV/Å)")
267
  with gr.Row():
268
  charge_rlx = gr.Slider(minimum=-10, maximum=10, value=0, step=1, label="Charge")
269
+ spin_rlx = gr.Slider(minimum=1, maximum=11, value=1, step=1, label="Spin")
270
  relax_cell = gr.Checkbox(False, label="Relax Unit Cell")
271
+ run_rlx_btn = gr.Button("Run Optimization", variant="primary")
272
+
273
  with gr.Column(variant="panel", min_width=520):
274
  rlx_status = gr.Textbox(label="Status", interactive=False)
275
+ rlx_traj = gr.File(label="Trajectory (.traj)", interactive=False)
276
+ rlx_viewer_placeholder = gr.HTML(visible=False)
277
+ rlx_html = gr.HTML(label="Final Structure")
278
+ rlx_log = gr.Textbox(label="Log", interactive=False, lines=15, max_lines=25)
279
+ rlx_script = gr.Code(label="Reproduction Script", language="python", interactive=False, lines=20, max_lines=30)
280
+ rlx_explain = gr.Markdown()
281
+
282
  run_rlx_btn.click(
283
  relax_wrapper,
284
  inputs=[xyz_rlx, steps_rlx, fmax_rlx, charge_rlx, spin_rlx, relax_cell],
285
+ outputs=[rlx_status, rlx_traj, rlx_log, rlx_script, rlx_explain, rlx_viewer_placeholder, rlx_html],
286
  )
287
 
288
  print("Starting OrbMol model loading…")
289
  _ = _load_orbmol_calc()
290
 
291
  if __name__ == "__main__":
292
+ demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)