Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import torch | |
| import numpy as np | |
| import tempfile | |
| import os | |
| from ase import Atoms | |
| from ase.io import read, write | |
| from ase.optimize import LBFGS | |
| from ase.md.velocitydistribution import MaxwellBoltzmannDistribution | |
| from ase.md.verlet import VelocityVerlet | |
| from ase.io.trajectory import Trajectory | |
| from ase.md import MDLogger | |
| from ase import units | |
| from orb_models.forcefield import pretrained | |
| from orb_models.forcefield.calculator import ORBCalculator | |
| # Install and import gradio_molecule3d | |
| try: | |
| from gradio_molecule3d import Molecule3D | |
| HAS_3D = True | |
| except ImportError: | |
| HAS_3D = False | |
| # Default molecular representations for 3D visualization | |
| DEFAULT_MOLECULAR_REPRESENTATIONS = [ | |
| { | |
| "model": 0, | |
| "chain": "", | |
| "resname": "", | |
| "style": "sphere", | |
| "color": "Jmol", | |
| "scale": 0.3, | |
| }, | |
| { | |
| "model": 0, | |
| "chain": "", | |
| "resname": "", | |
| "style": "stick", | |
| "color": "Jmol", | |
| "scale": 0.2, | |
| }, | |
| ] | |
| DEFAULT_MOLECULAR_SETTINGS = { | |
| "backgroundColor": "white", | |
| "orthographic": False, | |
| "disableFog": False, | |
| } | |
| # Global variable for the model | |
| model_calc = None | |
| def load_orbmol_model(): | |
| """Load OrbMol model once""" | |
| global model_calc | |
| if model_calc is None: | |
| try: | |
| print("Loading OrbMol model...") | |
| orbff = pretrained.orb_v3_conservative_inf_omat( | |
| device="cpu", | |
| precision="float32-high" | |
| ) | |
| model_calc = ORBCalculator(orbff, device="cpu") | |
| print("✅ OrbMol model loaded successfully") | |
| except Exception as e: | |
| print(f"❌ Error loading model: {e}") | |
| model_calc = None | |
| return model_calc | |
| def run_md_simulation(input_file, md_steps, prerelax_steps, md_timestep, temperature_k, md_ensemble, charge, spin_multiplicity, explanation_buffer=""): | |
| """Run molecular dynamics simulation similar to FAIR Chem UMA""" | |
| try: | |
| calc = load_orbmol_model() | |
| if calc is None: | |
| return None, "❌ Error: Could not load OrbMol model", "", explanation_buffer | |
| # Parse input file content | |
| if isinstance(input_file, str): | |
| # Handle XYZ string input | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f: | |
| f.write(input_file) | |
| xyz_file = f.name | |
| atoms = read(xyz_file) | |
| os.unlink(xyz_file) | |
| else: | |
| # Handle uploaded file | |
| atoms = read(input_file) | |
| # Set charge and spin | |
| atoms.info = {"charge": int(charge), "spin": int(spin_multiplicity)} | |
| atoms.calc = calc | |
| # Pre-relaxation | |
| if prerelax_steps > 0: | |
| opt = LBFGS(atoms) | |
| opt.run(fmax=0.05, steps=prerelax_steps) | |
| # Create trajectory file | |
| traj_path = tempfile.NamedTemporaryFile(suffix='.traj', delete=False).name | |
| # Initialize velocity distribution | |
| MaxwellBoltzmannDistribution(atoms, temperature_K=temperature_k) | |
| # Set up MD | |
| if md_ensemble == "NVE": | |
| dyn = VelocityVerlet(atoms, timestep=md_timestep * units.fs) | |
| else: # NVT | |
| from ase.md.langevin import Langevin | |
| dyn = Langevin(atoms, md_timestep * units.fs, temperature_K=temperature_k, friction=0.001/units.fs) | |
| # Attach trajectory | |
| traj = Trajectory(traj_path, "w", atoms) | |
| dyn.attach(traj.write, interval=1) | |
| # Run simulation | |
| dyn.run(md_steps) | |
| traj.close() | |
| # Generate log | |
| log_content = f"""OrbMol Molecular Dynamics Simulation | |
| ===================================== | |
| System: {len(atoms)} atoms | |
| MD Steps: {md_steps} | |
| Temperature: {temperature_k} K | |
| Ensemble: {md_ensemble} | |
| Timestep: {md_timestep} fs | |
| Charge: {charge} | |
| Spin Multiplicity: {spin_multiplicity} | |
| Final Energy: {atoms.get_potential_energy():.6f} eV | |
| Simulation completed successfully! | |
| """ | |
| # Generate reproduction script | |
| script_content = f"""# OrbMol MD Simulation Script | |
| import ase.io | |
| from ase.md.velocitydistribution import MaxwellBoltzmannDistribution | |
| from ase.md.verlet import VelocityVerlet | |
| from ase.optimize import LBFGS | |
| from ase.io.trajectory import Trajectory | |
| from ase import units | |
| from orb_models.forcefield import pretrained | |
| from orb_models.forcefield.calculator import ORBCalculator | |
| # Load structure | |
| atoms = ase.io.read('input_structure.xyz') # Your input file | |
| atoms.info = {{"charge": {charge}, "spin": {spin_multiplicity}}} | |
| # Set up OrbMol calculator | |
| orbff = pretrained.orb_v3_conservative_inf_omat(device="cpu") | |
| atoms.calc = ORBCalculator(orbff, device="cpu") | |
| # Pre-relaxation | |
| opt = LBFGS(atoms) | |
| opt.run(fmax=0.05, steps={prerelax_steps}) | |
| # Initialize velocities | |
| MaxwellBoltzmannDistribution(atoms, temperature_K={temperature_k}) | |
| # Set up MD | |
| dyn = VelocityVerlet(atoms, timestep={md_timestep} * units.fs) | |
| traj = Trajectory("md_output.traj", "w", atoms) | |
| dyn.attach(traj.write, interval=1) | |
| # Run simulation | |
| dyn.run({md_steps}) | |
| """ | |
| 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." | |
| return traj_path, log_content, script_content, explanation | |
| except Exception as e: | |
| return None, f"❌ Error during MD simulation: {str(e)}", "", "Error occurred" | |
| def run_optimization(input_file, optimization_steps, fmax, charge, spin_multiplicity): | |
| """Run geometry optimization""" | |
| try: | |
| calc = load_orbmol_model() | |
| if calc is None: | |
| return None, "❌ Error: Could not load OrbMol model", "" | |
| # Parse input | |
| if isinstance(input_file, str): | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f: | |
| f.write(input_file) | |
| xyz_file = f.name | |
| atoms = read(xyz_file) | |
| os.unlink(xyz_file) | |
| else: | |
| atoms = read(input_file) | |
| atoms.info = {"charge": int(charge), "spin": int(spin_multiplicity)} | |
| atoms.calc = calc | |
| # Create trajectory file | |
| traj_path = tempfile.NamedTemporaryFile(suffix='.traj', delete=False).name | |
| # Optimize | |
| opt = LBFGS(atoms, trajectory=traj_path) | |
| opt.run(fmax=fmax, steps=optimization_steps) | |
| # Generate log | |
| log_content = f"""OrbMol Geometry Optimization | |
| =========================== | |
| System: {len(atoms)} atoms | |
| Max Steps: {optimization_steps} | |
| Force Convergence: {fmax} eV/Å | |
| Charge: {charge} | |
| Spin Multiplicity: {spin_multiplicity} | |
| Final Energy: {atoms.get_potential_energy():.6f} eV | |
| Max Force: {np.max(np.linalg.norm(atoms.get_forces(), axis=1)):.6f} eV/Å | |
| Optimization completed! | |
| """ | |
| script_content = f"""# OrbMol Geometry Optimization Script | |
| import ase.io | |
| from ase.optimize import LBFGS | |
| from orb_models.forcefield import pretrained | |
| from orb_models.forcefield.calculator import ORBCalculator | |
| atoms = ase.io.read('input_structure.xyz') | |
| atoms.info = {{"charge": {charge}, "spin": {spin_multiplicity}}} | |
| orbff = pretrained.orb_v3_conservative_inf_omat(device="cpu") | |
| atoms.calc = ORBCalculator(orbff, device="cpu") | |
| opt = LBFGS(atoms, trajectory="optimization.traj") | |
| opt.run(fmax={fmax}, steps={optimization_steps}) | |
| """ | |
| return traj_path, log_content, script_content | |
| except Exception as e: | |
| return None, f"❌ Error during optimization: {str(e)}", "" | |
| def predict_single_point(xyz_content, charge=0, spin_multiplicity=1): | |
| """Single point energy and forces calculation""" | |
| try: | |
| calc = load_orbmol_model() | |
| if calc is None: | |
| return "❌ Error: Could not load OrbMol model", "" | |
| if not xyz_content.strip(): | |
| return "❌ Error: Please enter XYZ coordinates", "" | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.xyz', delete=False) as f: | |
| f.write(xyz_content) | |
| xyz_file = f.name | |
| atoms = read(xyz_file) | |
| atoms.info = {"charge": int(charge), "spin": int(spin_multiplicity)} | |
| atoms.calc = calc | |
| energy = atoms.get_potential_energy() | |
| forces = atoms.get_forces() | |
| result = f"""🔋 **Total Energy**: {energy:.6f} eV | |
| ⚡ **Atomic Forces**: | |
| """ | |
| for i, force in enumerate(forces): | |
| result += f"Atom {i+1}: [{force[0]:.4f}, {force[1]:.4f}, {force[2]:.4f}] eV/Å\n" | |
| max_force = np.max(np.linalg.norm(forces, axis=1)) | |
| result += f"\n📊 **Max Force**: {max_force:.4f} eV/Å" | |
| os.unlink(xyz_file) | |
| return result, "✅ Calculation completed with OrbMol" | |
| except Exception as e: | |
| return f"❌ Error during calculation: {str(e)}", "Error" | |
| # Predefined examples | |
| examples_md = [ | |
| ["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"], | |
| ["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"], | |
| ] | |
| examples_sp = [ | |
| ["""2 | |
| Hydrogen molecule | |
| H 0.0 0.0 0.0 | |
| H 0.0 0.0 0.74""", 0, 1], | |
| ["""3 | |
| Water molecule | |
| O 0.0000 0.0000 0.0000 | |
| H 0.7571 0.0000 0.5864 | |
| H -0.7571 0.0000 0.5864""", 0, 1], | |
| ["""5 | |
| Methane | |
| C 0.0000 0.0000 0.0000 | |
| H 1.0890 0.0000 0.0000 | |
| H -0.3630 1.0267 0.0000 | |
| H -0.3630 -0.5133 0.8887 | |
| H -0.3630 -0.5133 -0.8887""", 0, 1] | |
| ] | |
| # Main Gradio interface | |
| with gr.Blocks(theme=gr.themes.Ocean(), title="OrbMol Demo") as demo: | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| with gr.Column(variant="panel"): | |
| gr.Markdown("# OrbMol Demo - Quantum-Accurate Molecular Predictions") | |
| with gr.Tab("1. OrbMol Intro"): | |
| gr.Markdown(""" | |
| **OrbMol** is a neural network potential trained on the **OMol25** dataset (100M+ high-accuracy DFT calculations). | |
| Predicts **energies** and **forces** with quantum accuracy, optimized for: | |
| * 🧬 Biomolecules | |
| * ⚗️ Metal complexes | |
| * 🔋 Electrolytes | |
| Try the examples below to see OrbMol in action! | |
| """) | |
| # Quick examples for MD | |
| gr.Examples( | |
| examples=examples_md, | |
| inputs=[ | |
| gr.Textbox(visible=False, value=""), # Will be set by interface | |
| gr.Slider(visible=False), | |
| gr.Slider(visible=False), | |
| gr.Slider(visible=False), | |
| gr.Slider(visible=False), | |
| gr.Radio(visible=False), | |
| gr.Slider(visible=False), | |
| gr.Slider(visible=False), | |
| gr.Textbox(visible=False) | |
| ], | |
| outputs=[ | |
| gr.File(visible=False), | |
| gr.Code(visible=False), | |
| gr.Code(visible=False), | |
| gr.Markdown(visible=False) | |
| ], | |
| fn=run_md_simulation, | |
| cache_examples=True, | |
| label="Try molecular dynamics examples!" | |
| ) | |
| with gr.Tab("2. Single Point Calculations"): | |
| gr.Markdown("Calculate energy and forces for a single molecular geometry:") | |
| # Single point interface | |
| with gr.Row(): | |
| with gr.Column(): | |
| xyz_input_sp = gr.Textbox( | |
| label="XYZ Coordinates", | |
| placeholder="""3 | |
| Water molecule | |
| O 0.0 0.0 0.0 | |
| H 0.76 0.0 0.59 | |
| H -0.76 0.0 0.59""", | |
| lines=8 | |
| ) | |
| with gr.Row(): | |
| charge_sp = gr.Slider(value=0, label="Total Charge", minimum=-10, maximum=10, step=1) | |
| spin_sp = gr.Slider(value=1, label="Spin Multiplicity", minimum=1, maximum=11, step=1) | |
| predict_btn_sp = gr.Button("Calculate Energy & Forces", variant="primary") | |
| with gr.Column(): | |
| results_sp = gr.Textbox(label="Results", lines=12, interactive=False) | |
| status_sp = gr.Textbox(label="Status", max_lines=1, interactive=False) | |
| gr.Examples( | |
| examples=examples_sp, | |
| inputs=[xyz_input_sp, charge_sp, spin_sp], | |
| label="Single point examples" | |
| ) | |
| with gr.Tab("3. Molecular Dynamics"): | |
| gr.Markdown("Run molecular dynamics simulations with OrbMol:") | |
| xyz_input_md = gr.Textbox( | |
| label="XYZ Coordinates", | |
| placeholder="""3 | |
| Water molecule | |
| O 0.0 0.0 0.0 | |
| H 0.76 0.0 0.59 | |
| H -0.76 0.0 0.59""", | |
| lines=8 | |
| ) | |
| with gr.Row(): | |
| md_steps = gr.Slider(minimum=10, maximum=500, value=100, label="MD Steps") | |
| prerelax_steps = gr.Slider(minimum=0, maximum=100, value=20, label="Pre-Relaxation Steps") | |
| with gr.Row(): | |
| temperature_k = gr.Slider(minimum=0, maximum=1500, value=300, label="Temperature [K]") | |
| md_timestep = gr.Slider(minimum=0.1, maximum=5.0, value=1.0, label="Timestep [fs]") | |
| md_ensemble = gr.Radio(choices=["NVE", "NVT"], value="NVE", label="Ensemble") | |
| with gr.Row(): | |
| charge_md = gr.Slider(value=0, label="Total Charge", minimum=-10, maximum=10, step=1) | |
| spin_md = gr.Slider(value=1, label="Spin Multiplicity", minimum=1, maximum=11, step=1) | |
| md_button = gr.Button("Run MD Simulation", variant="primary") | |
| with gr.Tab("4. Geometry Optimization"): | |
| gr.Markdown("Optimize molecular geometries with OrbMol:") | |
| xyz_input_opt = gr.Textbox( | |
| label="XYZ Coordinates", | |
| placeholder="""3 | |
| Water molecule | |
| O 0.0 0.0 0.0 | |
| H 0.76 0.0 0.59 | |
| H -0.76 0.0 0.59""", | |
| lines=8 | |
| ) | |
| with gr.Row(): | |
| opt_steps = gr.Slider(minimum=1, maximum=500, value=300, label="Max Steps") | |
| fmax = gr.Slider(minimum=0.001, maximum=0.5, value=0.05, label="Force Tolerance [eV/Å]") | |
| with gr.Row(): | |
| charge_opt = gr.Slider(value=0, label="Total Charge", minimum=-10, maximum=10, step=1) | |
| spin_opt = gr.Slider(value=1, label="Spin Multiplicity", minimum=1, maximum=11, step=1) | |
| opt_button = gr.Button("Run Optimization", variant="primary") | |
| # Results panel | |
| with gr.Column(variant="panel", elem_id="results", min_width=500): | |
| gr.Markdown("## OrbMol Simulation Results") | |
| with gr.Tab("Visualization"): | |
| if HAS_3D: | |
| output_structure = Molecule3D( | |
| label="Simulation Visualization", | |
| reps=DEFAULT_MOLECULAR_REPRESENTATIONS, | |
| config=DEFAULT_MOLECULAR_SETTINGS, | |
| height=500, | |
| interactive=False, | |
| ) | |
| else: | |
| gr.Markdown("3D visualization not available. Install gradio-molecule3d for 3D viewing.") | |
| output_traj = gr.File(label="Trajectory File", interactive=False) | |
| explanation = gr.Markdown("Run a simulation to see results here!") | |
| with gr.Tab("Log"): | |
| output_text = gr.Code(lines=20, max_lines=30, label="Simulation Log", interactive=False) | |
| with gr.Tab("Script"): | |
| reproduction_script = gr.Code( | |
| interactive=False, | |
| max_lines=30, | |
| language="python", | |
| label="Reproduction Script", | |
| ) | |
| # Sidebar | |
| with gr.Sidebar(open=True): | |
| gr.Markdown("## Learn more about OrbMol") | |
| with gr.Accordion("What is OrbMol?", open=False): | |
| gr.Markdown(""" | |
| * OrbMol is a neural network potential for molecular property prediction | |
| * Built on Orb-v3 architecture, trained on OMol25 dataset (100M+ DFT calculations) | |
| * Supports charge and spin multiplicity for accurate molecular modeling | |
| * Optimized for biomolecules, metal complexes, and electrolytes | |
| [Read more about OrbMol](https://orbitalmaterials.com/posts/orbmol-extending-orb-to-molecular-systems) | |
| """) | |
| with gr.Accordion("Model Disclaimers", open=False): | |
| gr.Markdown(""" | |
| * OrbMol has limitations and may not work perfectly for all systems | |
| * Always validate results for your specific use case | |
| * Consider the limitations of the training data and methodology | |
| """) | |
| with gr.Accordion("Open source packages", open=False): | |
| gr.Markdown(""" | |
| * Model: [orbital-materials/orb-models](https://github.com/orbital-materials/orb-models) | |
| * Uses ASE, Gradio, and other open source packages | |
| * Licensed under Apache 2.0 | |
| """) | |
| # Connect buttons to functions | |
| predict_btn_sp.click( | |
| predict_single_point, | |
| inputs=[xyz_input_sp, charge_sp, spin_sp], | |
| outputs=[results_sp, status_sp] | |
| ) | |
| md_button.click( | |
| run_md_simulation, | |
| inputs=[xyz_input_md, md_steps, prerelax_steps, md_timestep, temperature_k, md_ensemble, charge_md, spin_md], | |
| outputs=[output_traj, output_text, reproduction_script, explanation] | |
| ) | |
| opt_button.click( | |
| run_optimization, | |
| inputs=[xyz_input_opt, opt_steps, fmax, charge_opt, spin_opt], | |
| outputs=[output_traj, output_text, reproduction_script] | |
| ) | |
| # Update 3D visualization when trajectory changes | |
| if HAS_3D: | |
| output_traj.change( | |
| lambda x: x, | |
| inputs=[output_traj], | |
| outputs=[output_structure] | |
| ) | |
| # Load model on startup | |
| print("🚀 Loading OrbMol model...") | |
| load_orbmol_model() | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| show_error=True | |
| ) |