annabossler commited on
Commit
c03a935
·
verified ·
1 Parent(s): e3cdc6b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +399 -92
app.py CHANGED
@@ -1,13 +1,52 @@
1
  import gradio as gr
2
  import torch
3
  import numpy as np
4
- from ase import Atoms
5
- from ase.io import read, write
6
  import tempfile
7
  import os
 
 
 
 
 
 
 
 
8
  from orb_models.forcefield import pretrained
9
  from orb_models.forcefield.calculator import ORBCalculator
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  # Global variable for the model
12
  model_calc = None
13
 
@@ -28,12 +67,177 @@ def load_orbmol_model():
28
  model_calc = None
29
  return model_calc
30
 
31
- def predict_molecule(xyz_content, charge=0, spin_multiplicity=1):
32
- """
33
- Main function: XYZ → OrbMol → Results
34
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
- # Load model
37
  calc = load_orbmol_model()
38
  if calc is None:
39
  return "❌ Error: Could not load OrbMol model", ""
@@ -41,51 +245,40 @@ def predict_molecule(xyz_content, charge=0, spin_multiplicity=1):
41
  if not xyz_content.strip():
42
  return "❌ Error: Please enter XYZ coordinates", ""
43
 
44
- # Create temporary file with XYZ
45
  with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
46
  f.write(xyz_content)
47
  xyz_file = f.name
48
 
49
- # Read molecular structure
50
  atoms = read(xyz_file)
51
-
52
- # Configure charge and spin (IMPORTANT for OrbMol!)
53
- atoms.info = {
54
- "charge": int(charge),
55
- "spin": int(spin_multiplicity)
56
- }
57
-
58
- # Assign OrbMol calculator
59
  atoms.calc = calc
60
 
61
- # Make the prediction!
62
- energy = atoms.get_potential_energy() # In eV
63
- forces = atoms.get_forces() # In eV/Å
64
 
65
- # Format results nicely
66
- result = f"""
67
- 🔋 **Total Energy**: {energy:.6f} eV
68
 
69
  ⚡ **Atomic Forces**:
70
  """
71
-
72
  for i, force in enumerate(forces):
73
  result += f"Atom {i+1}: [{force[0]:.4f}, {force[1]:.4f}, {force[2]:.4f}] eV/Å\n"
74
 
75
- # Additional statistics
76
  max_force = np.max(np.linalg.norm(forces, axis=1))
77
  result += f"\n📊 **Max Force**: {max_force:.4f} eV/Å"
78
 
79
- # Clean up temporary file
80
  os.unlink(xyz_file)
81
-
82
  return result, "✅ Calculation completed with OrbMol"
83
 
84
  except Exception as e:
85
  return f"❌ Error during calculation: {str(e)}", "Error"
86
 
87
  # Predefined examples
88
- examples = [
 
 
 
 
 
89
  ["""2
90
  Hydrogen molecule
91
  H 0.0 0.0 0.0
@@ -97,7 +290,7 @@ O 0.0000 0.0000 0.0000
97
  H 0.7571 0.0000 0.5864
98
  H -0.7571 0.0000 0.5864""", 0, 1],
99
 
100
- ["""4
101
  Methane
102
  C 0.0000 0.0000 0.0000
103
  H 1.0890 0.0000 0.0000
@@ -106,7 +299,7 @@ H -0.3630 -0.5133 0.8887
106
  H -0.3630 -0.5133 -0.8887""", 0, 1]
107
  ]
108
 
109
- # Gradio interface - using FAIR Chem UMA style
110
  with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
111
 
112
  with gr.Row():
@@ -114,101 +307,215 @@ with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
114
  with gr.Column(variant="panel"):
115
  gr.Markdown("# OrbMol Demo - Quantum-Accurate Molecular Predictions")
116
 
117
- gr.Markdown("""
118
- **OrbMol** is a neural network potential trained on the **OMol25** dataset (100M+ high-accuracy DFT calculations).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- Predicts **energies** and **forces** with quantum accuracy, optimized for:
121
- * 🧬 Biomolecules
122
- * ⚗️ Metal complexes
123
- * 🔋 Electrolytes
124
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- gr.Markdown("## Simulation inputs")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- with gr.Column(variant="panel"):
129
- gr.Markdown("### Input molecular structure")
130
 
131
- xyz_input = gr.Textbox(
132
  label="XYZ Coordinates",
133
  placeholder="""3
134
  Water molecule
135
- O 0.0000 0.0000 0.0000
136
- H 0.7571 0.0000 0.5864
137
- H -0.7571 0.0000 0.5864""",
138
- lines=12,
139
- info="Paste XYZ coordinates of your molecule here"
140
  )
141
 
142
- gr.Markdown("OMol-specific settings for total charge and spin multiplicity")
143
  with gr.Row():
144
- charge_input = gr.Slider(
145
- value=0, label="Total Charge", minimum=-10, maximum=10, step=1
146
- )
147
- spin_input = gr.Slider(
148
- value=1, maximum=11, minimum=1, step=1, label="Spin Multiplicity"
149
- )
150
 
151
- predict_btn = gr.Button("Run OrbMol Prediction", variant="primary", size="lg")
 
 
152
 
 
 
 
153
  with gr.Column(variant="panel", elem_id="results", min_width=500):
154
- gr.Markdown("## OrbMol Prediction Results")
155
 
156
- results_output = gr.Textbox(
157
- label="Energy & Forces",
158
- lines=15,
159
- interactive=False,
160
- info="OrbMol energy and force predictions"
161
- )
 
 
 
 
 
 
 
 
 
162
 
163
- status_output = gr.Textbox(
164
- label="Status",
165
- interactive=False,
166
- max_lines=1
167
- )
168
-
169
- # Examples section
170
- gr.Markdown("### 🧪 Try These Examples")
171
- gr.Examples(
172
- examples=examples,
173
- inputs=[xyz_input, charge_input, spin_input],
174
- label="Click any example to load it"
175
- )
176
-
177
- # Connect button to function
178
- predict_btn.click(
179
- predict_molecule,
180
- inputs=[xyz_input, charge_input, spin_input],
181
- outputs=[results_output, status_output]
182
- )
183
 
184
- # Footer info - matching FAIR Chem UMA style
185
  with gr.Sidebar(open=True):
186
  gr.Markdown("## Learn more about OrbMol")
 
187
  with gr.Accordion("What is OrbMol?", open=False):
188
  gr.Markdown("""
189
- * OrbMol is a neural network potential for molecular property prediction with quantum-level accuracy
190
- * Built on the Orb-v3 architecture and trained on OMol25 dataset (100M+ DFT calculations)
 
191
  * Optimized for biomolecules, metal complexes, and electrolytes
192
- * Supports configurable charge and spin multiplicity
193
 
194
  [Read more about OrbMol](https://orbitalmaterials.com/posts/orbmol-extending-orb-to-molecular-systems)
195
  """)
196
-
197
  with gr.Accordion("Model Disclaimers", open=False):
198
  gr.Markdown("""
199
- * While OrbMol represents significant progress in molecular ML potentials, the model has limitations
200
  * Always validate results for your specific use case
201
- * Consider the limitations of the ωB97M-V/def2-TZVPD level of theory used in training
202
  """)
203
-
204
  with gr.Accordion("Open source packages", open=False):
205
  gr.Markdown("""
206
- * Model code available at [orbital-materials/orb-models](https://github.com/orbital-materials/orb-models)
207
- * This demo uses ASE, Gradio, and other open source packages
 
208
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
  # Load model on startup
211
- print("🚀 Starting OrbMol model loading...")
212
  load_orbmol_model()
213
 
214
  if __name__ == "__main__":
 
1
  import gradio as gr
2
  import torch
3
  import numpy as np
 
 
4
  import tempfile
5
  import os
6
+ from ase import Atoms
7
+ from ase.io import read, write
8
+ from ase.optimize import LBFGS
9
+ from ase.md.velocitydistribution import MaxwellBoltzmannDistribution
10
+ from ase.md.verlet import VelocityVerlet
11
+ from ase.io.trajectory import Trajectory
12
+ from ase.md import MDLogger
13
+ from ase import units
14
  from orb_models.forcefield import pretrained
15
  from orb_models.forcefield.calculator import ORBCalculator
16
 
17
+ # Install and import gradio_molecule3d
18
+ try:
19
+ from gradio_molecule3d import Molecule3D
20
+ HAS_3D = True
21
+ except ImportError:
22
+ HAS_3D = False
23
+
24
+ # Default molecular representations for 3D visualization
25
+ DEFAULT_MOLECULAR_REPRESENTATIONS = [
26
+ {
27
+ "model": 0,
28
+ "chain": "",
29
+ "resname": "",
30
+ "style": "sphere",
31
+ "color": "Jmol",
32
+ "scale": 0.3,
33
+ },
34
+ {
35
+ "model": 0,
36
+ "chain": "",
37
+ "resname": "",
38
+ "style": "stick",
39
+ "color": "Jmol",
40
+ "scale": 0.2,
41
+ },
42
+ ]
43
+
44
+ DEFAULT_MOLECULAR_SETTINGS = {
45
+ "backgroundColor": "white",
46
+ "orthographic": False,
47
+ "disableFog": False,
48
+ }
49
+
50
  # Global variable for the model
51
  model_calc = None
52
 
 
67
  model_calc = None
68
  return model_calc
69
 
70
+ def run_md_simulation(input_file, md_steps, prerelax_steps, md_timestep, temperature_k, md_ensemble, charge, spin_multiplicity, explanation_buffer=""):
71
+ """Run molecular dynamics simulation similar to FAIR Chem UMA"""
72
+ try:
73
+ calc = load_orbmol_model()
74
+ if calc is None:
75
+ return None, "❌ Error: Could not load OrbMol model", "", explanation_buffer
76
+
77
+ # Parse input file content
78
+ if isinstance(input_file, str):
79
+ # Handle XYZ string input
80
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
81
+ f.write(input_file)
82
+ xyz_file = f.name
83
+ atoms = read(xyz_file)
84
+ os.unlink(xyz_file)
85
+ else:
86
+ # Handle uploaded file
87
+ atoms = read(input_file)
88
+
89
+ # Set charge and spin
90
+ atoms.info = {"charge": int(charge), "spin": int(spin_multiplicity)}
91
+ atoms.calc = calc
92
+
93
+ # Pre-relaxation
94
+ if prerelax_steps > 0:
95
+ opt = LBFGS(atoms)
96
+ opt.run(fmax=0.05, steps=prerelax_steps)
97
+
98
+ # Create trajectory file
99
+ traj_path = tempfile.NamedTemporaryFile(suffix='.traj', delete=False).name
100
+
101
+ # Initialize velocity distribution
102
+ MaxwellBoltzmannDistribution(atoms, temperature_K=temperature_k)
103
+
104
+ # Set up MD
105
+ if md_ensemble == "NVE":
106
+ dyn = VelocityVerlet(atoms, timestep=md_timestep * units.fs)
107
+ else: # NVT
108
+ from ase.md.langevin import Langevin
109
+ dyn = Langevin(atoms, md_timestep * units.fs, temperature_K=temperature_k, friction=0.001/units.fs)
110
+
111
+ # Attach trajectory
112
+ traj = Trajectory(traj_path, "w", atoms)
113
+ dyn.attach(traj.write, interval=1)
114
+
115
+ # Run simulation
116
+ dyn.run(md_steps)
117
+ traj.close()
118
+
119
+ # Generate log
120
+ log_content = f"""OrbMol Molecular Dynamics Simulation
121
+ =====================================
122
+ System: {len(atoms)} atoms
123
+ MD Steps: {md_steps}
124
+ Temperature: {temperature_k} K
125
+ Ensemble: {md_ensemble}
126
+ Timestep: {md_timestep} fs
127
+ Charge: {charge}
128
+ Spin Multiplicity: {spin_multiplicity}
129
+
130
+ Final Energy: {atoms.get_potential_energy():.6f} eV
131
+ Simulation completed successfully!
132
+ """
133
+
134
+ # Generate reproduction script
135
+ script_content = f"""# OrbMol MD Simulation Script
136
+ import ase.io
137
+ from ase.md.velocitydistribution import MaxwellBoltzmannDistribution
138
+ from ase.md.verlet import VelocityVerlet
139
+ from ase.optimize import LBFGS
140
+ from ase.io.trajectory import Trajectory
141
+ from ase import units
142
+ from orb_models.forcefield import pretrained
143
+ from orb_models.forcefield.calculator import ORBCalculator
144
+
145
+ # Load structure
146
+ atoms = ase.io.read('input_structure.xyz') # Your input file
147
+ atoms.info = {{"charge": {charge}, "spin": {spin_multiplicity}}}
148
+
149
+ # Set up OrbMol calculator
150
+ orbff = pretrained.orb_v3_conservative_inf_omat(device="cpu")
151
+ atoms.calc = ORBCalculator(orbff, device="cpu")
152
+
153
+ # Pre-relaxation
154
+ opt = LBFGS(atoms)
155
+ opt.run(fmax=0.05, steps={prerelax_steps})
156
+
157
+ # Initialize velocities
158
+ MaxwellBoltzmannDistribution(atoms, temperature_K={temperature_k})
159
+
160
+ # Set up MD
161
+ dyn = VelocityVerlet(atoms, timestep={md_timestep} * units.fs)
162
+ traj = Trajectory("md_output.traj", "w", atoms)
163
+ dyn.attach(traj.write, interval=1)
164
+
165
+ # Run simulation
166
+ dyn.run({md_steps})
167
+ """
168
+
169
+ explanation = explanation_buffer if explanation_buffer else f"Molecular dynamics simulation completed! This shows {len(atoms)} atoms moving over {md_steps} steps at {temperature_k} K. The atoms are vibrating due to thermal motion, and you can see the molecular structure evolving over time."
170
+
171
+ return traj_path, log_content, script_content, explanation
172
+
173
+ except Exception as e:
174
+ return None, f"❌ Error during MD simulation: {str(e)}", "", "Error occurred"
175
+
176
+ def run_optimization(input_file, optimization_steps, fmax, charge, spin_multiplicity):
177
+ """Run geometry optimization"""
178
+ try:
179
+ calc = load_orbmol_model()
180
+ if calc is None:
181
+ return None, "❌ Error: Could not load OrbMol model", ""
182
+
183
+ # Parse input
184
+ if isinstance(input_file, str):
185
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
186
+ f.write(input_file)
187
+ xyz_file = f.name
188
+ atoms = read(xyz_file)
189
+ os.unlink(xyz_file)
190
+ else:
191
+ atoms = read(input_file)
192
+
193
+ atoms.info = {"charge": int(charge), "spin": int(spin_multiplicity)}
194
+ atoms.calc = calc
195
+
196
+ # Create trajectory file
197
+ traj_path = tempfile.NamedTemporaryFile(suffix='.traj', delete=False).name
198
+
199
+ # Optimize
200
+ opt = LBFGS(atoms, trajectory=traj_path)
201
+ opt.run(fmax=fmax, steps=optimization_steps)
202
+
203
+ # Generate log
204
+ log_content = f"""OrbMol Geometry Optimization
205
+ ===========================
206
+ System: {len(atoms)} atoms
207
+ Max Steps: {optimization_steps}
208
+ Force Convergence: {fmax} eV/Å
209
+ Charge: {charge}
210
+ Spin Multiplicity: {spin_multiplicity}
211
+
212
+ Final Energy: {atoms.get_potential_energy():.6f} eV
213
+ Max Force: {np.max(np.linalg.norm(atoms.get_forces(), axis=1)):.6f} eV/Å
214
+ Optimization completed!
215
+ """
216
+
217
+ script_content = f"""# OrbMol Geometry Optimization Script
218
+ import ase.io
219
+ from ase.optimize import LBFGS
220
+ from orb_models.forcefield import pretrained
221
+ from orb_models.forcefield.calculator import ORBCalculator
222
+
223
+ atoms = ase.io.read('input_structure.xyz')
224
+ atoms.info = {{"charge": {charge}, "spin": {spin_multiplicity}}}
225
+
226
+ orbff = pretrained.orb_v3_conservative_inf_omat(device="cpu")
227
+ atoms.calc = ORBCalculator(orbff, device="cpu")
228
+
229
+ opt = LBFGS(atoms, trajectory="optimization.traj")
230
+ opt.run(fmax={fmax}, steps={optimization_steps})
231
+ """
232
+
233
+ return traj_path, log_content, script_content
234
+
235
+ except Exception as e:
236
+ return None, f"❌ Error during optimization: {str(e)}", ""
237
+
238
+ def predict_single_point(xyz_content, charge=0, spin_multiplicity=1):
239
+ """Single point energy and forces calculation"""
240
  try:
 
241
  calc = load_orbmol_model()
242
  if calc is None:
243
  return "❌ Error: Could not load OrbMol model", ""
 
245
  if not xyz_content.strip():
246
  return "❌ Error: Please enter XYZ coordinates", ""
247
 
 
248
  with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f:
249
  f.write(xyz_content)
250
  xyz_file = f.name
251
 
 
252
  atoms = read(xyz_file)
253
+ atoms.info = {"charge": int(charge), "spin": int(spin_multiplicity)}
 
 
 
 
 
 
 
254
  atoms.calc = calc
255
 
256
+ energy = atoms.get_potential_energy()
257
+ forces = atoms.get_forces()
 
258
 
259
+ result = f"""🔋 **Total Energy**: {energy:.6f} eV
 
 
260
 
261
  ⚡ **Atomic Forces**:
262
  """
 
263
  for i, force in enumerate(forces):
264
  result += f"Atom {i+1}: [{force[0]:.4f}, {force[1]:.4f}, {force[2]:.4f}] eV/Å\n"
265
 
 
266
  max_force = np.max(np.linalg.norm(forces, axis=1))
267
  result += f"\n📊 **Max Force**: {max_force:.4f} eV/Å"
268
 
 
269
  os.unlink(xyz_file)
 
270
  return result, "✅ Calculation completed with OrbMol"
271
 
272
  except Exception as e:
273
  return f"❌ Error during calculation: {str(e)}", "Error"
274
 
275
  # Predefined examples
276
+ examples_md = [
277
+ ["2\nHydrogen molecule\nH 0.0 0.0 0.0\nH 0.0 0.0 0.74", 100, 20, 1.0, 300.0, "NVE", 0, 1, "Simple H2 molecule dynamics showing thermal vibrations"],
278
+ ["3\nWater molecule\nO 0.0 0.0 0.0\nH 0.7571 0.0 0.5864\nH -0.7571 0.0 0.5864", 200, 20, 1.0, 300.0, "NVE", 0, 1, "Water molecule thermal motion and vibrations"],
279
+ ]
280
+
281
+ examples_sp = [
282
  ["""2
283
  Hydrogen molecule
284
  H 0.0 0.0 0.0
 
290
  H 0.7571 0.0000 0.5864
291
  H -0.7571 0.0000 0.5864""", 0, 1],
292
 
293
+ ["""5
294
  Methane
295
  C 0.0000 0.0000 0.0000
296
  H 1.0890 0.0000 0.0000
 
299
  H -0.3630 -0.5133 -0.8887""", 0, 1]
300
  ]
301
 
302
+ # Main Gradio interface
303
  with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo:
304
 
305
  with gr.Row():
 
307
  with gr.Column(variant="panel"):
308
  gr.Markdown("# OrbMol Demo - Quantum-Accurate Molecular Predictions")
309
 
310
+ with gr.Tab("1. OrbMol Intro"):
311
+ gr.Markdown("""
312
+ **OrbMol** is a neural network potential trained on the **OMol25** dataset (100M+ high-accuracy DFT calculations).
313
+
314
+ Predicts **energies** and **forces** with quantum accuracy, optimized for:
315
+ * 🧬 Biomolecules
316
+ * ⚗️ Metal complexes
317
+ * 🔋 Electrolytes
318
+
319
+ Try the examples below to see OrbMol in action!
320
+ """)
321
+
322
+ # Quick examples for MD
323
+ gr.Examples(
324
+ examples=examples_md,
325
+ inputs=[
326
+ gr.Textbox(visible=False, value=""), # Will be set by interface
327
+ gr.Slider(visible=False),
328
+ gr.Slider(visible=False),
329
+ gr.Slider(visible=False),
330
+ gr.Slider(visible=False),
331
+ gr.Radio(visible=False),
332
+ gr.Slider(visible=False),
333
+ gr.Slider(visible=False),
334
+ gr.Textbox(visible=False)
335
+ ],
336
+ outputs=[
337
+ gr.File(visible=False),
338
+ gr.Code(visible=False),
339
+ gr.Code(visible=False),
340
+ gr.Markdown(visible=False)
341
+ ],
342
+ fn=run_md_simulation,
343
+ cache_examples=True,
344
+ label="Try molecular dynamics examples!"
345
+ )
346
 
347
+ with gr.Tab("2. Single Point Calculations"):
348
+ gr.Markdown("Calculate energy and forces for a single molecular geometry:")
349
+
350
+ # Single point interface
351
+ with gr.Row():
352
+ with gr.Column():
353
+ xyz_input_sp = gr.Textbox(
354
+ label="XYZ Coordinates",
355
+ placeholder="""3
356
+ Water molecule
357
+ O 0.0 0.0 0.0
358
+ H 0.76 0.0 0.59
359
+ H -0.76 0.0 0.59""",
360
+ lines=8
361
+ )
362
+
363
+ with gr.Row():
364
+ charge_sp = gr.Slider(value=0, label="Total Charge", minimum=-10, maximum=10, step=1)
365
+ spin_sp = gr.Slider(value=1, label="Spin Multiplicity", minimum=1, maximum=11, step=1)
366
+
367
+ predict_btn_sp = gr.Button("Calculate Energy & Forces", variant="primary")
368
+
369
+ with gr.Column():
370
+ results_sp = gr.Textbox(label="Results", lines=12, interactive=False)
371
+ status_sp = gr.Textbox(label="Status", max_lines=1, interactive=False)
372
+
373
+ gr.Examples(
374
+ examples=examples_sp,
375
+ inputs=[xyz_input_sp, charge_sp, spin_sp],
376
+ label="Single point examples"
377
+ )
378
 
379
+ with gr.Tab("3. Molecular Dynamics"):
380
+ gr.Markdown("Run molecular dynamics simulations with OrbMol:")
381
+
382
+ xyz_input_md = gr.Textbox(
383
+ label="XYZ Coordinates",
384
+ placeholder="""3
385
+ Water molecule
386
+ O 0.0 0.0 0.0
387
+ H 0.76 0.0 0.59
388
+ H -0.76 0.0 0.59""",
389
+ lines=8
390
+ )
391
+
392
+ with gr.Row():
393
+ md_steps = gr.Slider(minimum=10, maximum=500, value=100, label="MD Steps")
394
+ prerelax_steps = gr.Slider(minimum=0, maximum=100, value=20, label="Pre-Relaxation Steps")
395
+
396
+ with gr.Row():
397
+ temperature_k = gr.Slider(minimum=0, maximum=1500, value=300, label="Temperature [K]")
398
+ md_timestep = gr.Slider(minimum=0.1, maximum=5.0, value=1.0, label="Timestep [fs]")
399
+
400
+ md_ensemble = gr.Radio(choices=["NVE", "NVT"], value="NVE", label="Ensemble")
401
+
402
+ with gr.Row():
403
+ charge_md = gr.Slider(value=0, label="Total Charge", minimum=-10, maximum=10, step=1)
404
+ spin_md = gr.Slider(value=1, label="Spin Multiplicity", minimum=1, maximum=11, step=1)
405
+
406
+ md_button = gr.Button("Run MD Simulation", variant="primary")
407
 
408
+ with gr.Tab("4. Geometry Optimization"):
409
+ gr.Markdown("Optimize molecular geometries with OrbMol:")
410
 
411
+ xyz_input_opt = gr.Textbox(
412
  label="XYZ Coordinates",
413
  placeholder="""3
414
  Water molecule
415
+ O 0.0 0.0 0.0
416
+ H 0.76 0.0 0.59
417
+ H -0.76 0.0 0.59""",
418
+ lines=8
 
419
  )
420
 
 
421
  with gr.Row():
422
+ opt_steps = gr.Slider(minimum=1, maximum=500, value=300, label="Max Steps")
423
+ fmax = gr.Slider(minimum=0.001, maximum=0.5, value=0.05, label="Force Tolerance [eV/Å]")
 
 
 
 
424
 
425
+ with gr.Row():
426
+ charge_opt = gr.Slider(value=0, label="Total Charge", minimum=-10, maximum=10, step=1)
427
+ spin_opt = gr.Slider(value=1, label="Spin Multiplicity", minimum=1, maximum=11, step=1)
428
 
429
+ opt_button = gr.Button("Run Optimization", variant="primary")
430
+
431
+ # Results panel
432
  with gr.Column(variant="panel", elem_id="results", min_width=500):
433
+ gr.Markdown("## OrbMol Simulation Results")
434
 
435
+ with gr.Tab("Visualization"):
436
+ if HAS_3D:
437
+ output_structure = Molecule3D(
438
+ label="Simulation Visualization",
439
+ reps=DEFAULT_MOLECULAR_REPRESENTATIONS,
440
+ config=DEFAULT_MOLECULAR_SETTINGS,
441
+ height=500,
442
+ interactive=False,
443
+ )
444
+ else:
445
+ gr.Markdown("3D visualization not available. Install gradio-molecule3d for 3D viewing.")
446
+
447
+ output_traj = gr.File(label="Trajectory File", interactive=False)
448
+
449
+ explanation = gr.Markdown("Run a simulation to see results here!")
450
 
451
+ with gr.Tab("Log"):
452
+ output_text = gr.Code(lines=20, max_lines=30, label="Simulation Log", interactive=False)
453
+
454
+ with gr.Tab("Script"):
455
+ reproduction_script = gr.Code(
456
+ interactive=False,
457
+ max_lines=30,
458
+ language="python",
459
+ label="Reproduction Script",
460
+ )
 
 
 
 
 
 
 
 
 
 
461
 
462
+ # Sidebar
463
  with gr.Sidebar(open=True):
464
  gr.Markdown("## Learn more about OrbMol")
465
+
466
  with gr.Accordion("What is OrbMol?", open=False):
467
  gr.Markdown("""
468
+ * OrbMol is a neural network potential for molecular property prediction
469
+ * Built on Orb-v3 architecture, trained on OMol25 dataset (100M+ DFT calculations)
470
+ * Supports charge and spin multiplicity for accurate molecular modeling
471
  * Optimized for biomolecules, metal complexes, and electrolytes
 
472
 
473
  [Read more about OrbMol](https://orbitalmaterials.com/posts/orbmol-extending-orb-to-molecular-systems)
474
  """)
475
+
476
  with gr.Accordion("Model Disclaimers", open=False):
477
  gr.Markdown("""
478
+ * OrbMol has limitations and may not work perfectly for all systems
479
  * Always validate results for your specific use case
480
+ * Consider the limitations of the training data and methodology
481
  """)
482
+
483
  with gr.Accordion("Open source packages", open=False):
484
  gr.Markdown("""
485
+ * Model: [orbital-materials/orb-models](https://github.com/orbital-materials/orb-models)
486
+ * Uses ASE, Gradio, and other open source packages
487
+ * Licensed under Apache 2.0
488
  """)
489
+
490
+ # Connect buttons to functions
491
+ predict_btn_sp.click(
492
+ predict_single_point,
493
+ inputs=[xyz_input_sp, charge_sp, spin_sp],
494
+ outputs=[results_sp, status_sp]
495
+ )
496
+
497
+ md_button.click(
498
+ run_md_simulation,
499
+ inputs=[xyz_input_md, md_steps, prerelax_steps, md_timestep, temperature_k, md_ensemble, charge_md, spin_md],
500
+ outputs=[output_traj, output_text, reproduction_script, explanation]
501
+ )
502
+
503
+ opt_button.click(
504
+ run_optimization,
505
+ inputs=[xyz_input_opt, opt_steps, fmax, charge_opt, spin_opt],
506
+ outputs=[output_traj, output_text, reproduction_script]
507
+ )
508
+
509
+ # Update 3D visualization when trajectory changes
510
+ if HAS_3D:
511
+ output_traj.change(
512
+ lambda x: x,
513
+ inputs=[output_traj],
514
+ outputs=[output_structure]
515
+ )
516
 
517
  # Load model on startup
518
+ print("🚀 Loading OrbMol model...")
519
  load_orbmol_model()
520
 
521
  if __name__ == "__main__":