Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| from dotenv import load_dotenv | |
| import google.generativeai as genai | |
| import ase | |
| from ase.build import molecule # Often used, good to have for exec scope if needed | |
| from ase.calculators.emt import EMT # Common default | |
| from ase.optimize import BFGS # Common optimizer | |
| from ase.visualize.plot import plot_atoms | |
| import matplotlib | |
| matplotlib.use('Agg') # Use Agg backend for non-interactive plotting | |
| import matplotlib.pyplot as plt | |
| from PIL import Image | |
| import io | |
| import base64 | |
| import traceback | |
| import contextlib | |
| # Load environment variables from .env file (for GEMINI_API_KEY) | |
| load_dotenv() | |
| # --- Gemini API Configuration --- | |
| API_KEY = os.getenv("GEMINI_API_KEY") | |
| # MODEL = 'gemini-2.0-flash' | |
| MODEL = 'gemini-2.5-flash-preview-04-17' | |
| # MODEL = 'gemini-2.5-pro-exp-03-25' | |
| if not API_KEY: | |
| print("WARNING: GEMINI_API_KEY environment variable not set. App will not function.") | |
| else: | |
| try: | |
| genai.configure(api_key=API_KEY) | |
| MODEL = genai.GenerativeModel(MODEL) | |
| print("Gemini model initialized successfully.") | |
| except Exception as e: | |
| print(f"ERROR: Could not initialize Gemini model: {e}") | |
| MODEL = None | |
| # --- Prompts for Gemini --- | |
| PROMPT_FOR_ASE_CODE_GENERATION = """ | |
| You are an expert in molecular simulations using the Atomic Simulation Environment (ASE) and Python. | |
| A user wants to perform the following task: "{user_query}" | |
| Generate a complete and runnable Python script using ASE to perform this task. | |
| The script MUST: | |
| 1. Include all necessary import statements (e.g., `import ase`, `from ase.build import molecule`, `from ase.calculators.emt import EMT`, `from ase.optimize import BFGS`, etc.). Do not assume any modules are pre-imported. | |
| 2. Define the atomic structure. | |
| 3. Set up an appropriate ASE calculator. | |
| 4. Perform the simulation as requested (e.g., geometry optimization, molecular dynamics, single point energy calculation). If non is specified perform an appropriate simulation. | |
| 5. Ensure the final ASE `Atoms` object, representing the result of the simulation (e.g., optimized structure), is assigned to a variable named `atoms_object`. This is crucial for visualization. | |
| 6. Optionally, print any relevant information or results from the simulation to standard output (e.g., final energy, bond lengths, status messages). | |
| Provide ONLY the Python code block. Do not include any explanations, comments, or markdown formatting before or after the code block itself. | |
| The script will be executed in an environment where ASE and its dependencies are installed.""" | |
| PROMPT_FOR_ANALYSIS = """ | |
| You are an AI assistant specialized in interpreting results from molecular simulations performed with the Atomic Simulation Environment (ASE). | |
| The user's original simulation request was: | |
| "{user_query}" | |
| The following Python ASE code was generated and executed to address this request: | |
| ```python | |
| {generated_code} | |
| ``` | |
| The execution of this code produced the following standard output (stdout/stderr): | |
| ``` | |
| {simulation_output} | |
| ``` | |
| {structure_info} | |
| Based on all the above information, please provide a concise analysis: | |
| 1. Briefly explain what the simulation aimed to do and what steps were performed by the code. | |
| 2. Summarize the key results obtained from the simulation output (e.g., final energy, convergence status, important structural parameters if mentioned in output). | |
| 3. If a molecular structure was visualized, comment on it in relation to the simulation (e.g., "The visualized structure shows the molecule after optimization"). | |
| 4. Conclude whether the simulation likely achieved the user's original request. | |
| 5. If there were any errors or issues, please mention them. | |
| Present the analysis in a clear, easy-to-understand manner. Avoid overly technical jargon where possible. | |
| Provide ONLY the Markdown code block. Do not include any explanations, comments before or after the code block itself. | |
| """ | |
| # --- Helper Functions --- | |
| def plot_atoms_to_pil_image(atoms_obj): | |
| """Converts an ASE Atoms object to a PIL Image for display.""" | |
| if not isinstance(atoms_obj, ase.Atoms): | |
| return None | |
| try: | |
| fig, ax = plt.subplots() | |
| # Use ase.visualize.plot_atoms for plotting | |
| plot_atoms(atoms_obj, ax, radii=0.3, rotation=('0x,0y,0z')) | |
| ax.set_xlabel("X (Å)") | |
| ax.set_ylabel("Y (Å)") | |
| ax.axis('equal') | |
| buf = io.BytesIO() | |
| fig.savefig(buf, format='png', bbox_inches='tight') | |
| plt.close(fig) # Close the figure to free up memory | |
| buf.seek(0) | |
| return Image.open(buf) | |
| except Exception as e: | |
| print(f"Error plotting atoms: {traceback.format_exc()}") | |
| return None | |
| # --- Core Simulation Logic --- | |
| def simulate_and_analyze(user_query_str): | |
| if not MODEL: | |
| error_msg = "Gemini model not initialized. Please check API key and configuration." | |
| return "# Error: Model not initialized.", error_msg, None, error_msg | |
| generated_code_str = "" | |
| simulation_stdout_str = "" | |
| pil_image = None | |
| analysis_text_str = "" | |
| try: | |
| # 1. Generate ASE code from user query | |
| prompt_for_code = PROMPT_FOR_ASE_CODE_GENERATION.format(user_query=user_query_str) | |
| response_code = MODEL.generate_content(prompt_for_code) | |
| generated_code_str = response_code.text.strip() | |
| # Remove potential markdown backticks if Gemini adds them | |
| if generated_code_str.startswith("```python"): | |
| generated_code_str = generated_code_str[len("```python"):].strip() | |
| if generated_code_str.endswith("```"): | |
| generated_code_str = generated_code_str[:-len("```")].strip() | |
| except Exception as e: | |
| error_msg = f"Error generating ASE code with Gemini: {traceback.format_exc()}" | |
| return f"# Error: Could not generate ASE code.\n{error_msg}", error_msg, None, error_msg | |
| # 2. Execute generated ASE code | |
| atoms_object_from_exec = None | |
| execution_namespace = {} | |
| captured_output = io.StringIO() | |
| exec_error_msg = None | |
| try: | |
| with contextlib.redirect_stdout(captured_output): | |
| # Provide a minimal, controlled global scope for exec | |
| # ASE and its modules should be imported by the generated code itself | |
| exec(generated_code_str, {'__builtins__': __builtins__, 'ase': ase}, execution_namespace) | |
| simulation_stdout_str = captured_output.getvalue() | |
| atoms_object_from_exec = execution_namespace.get('atoms_object') | |
| if atoms_object_from_exec is not None and not isinstance(atoms_object_from_exec, ase.Atoms): | |
| warning_msg = f"Warning: 'atoms_object' was found but is not a valid ASE Atoms object (type: {type(atoms_object_from_exec)}). Visualization might fail." | |
| simulation_stdout_str += "\n" + warning_msg | |
| atoms_object_from_exec = None # Invalidate if not correct type | |
| elif 'atoms_object' not in execution_namespace: | |
| simulation_stdout_str += "\nWarning: 'atoms_object' variable not found after code execution. Visualization will not be available." | |
| except Exception as e: | |
| exec_error_msg = f"Error during ASE code execution:\n{traceback.format_exc()}" | |
| simulation_stdout_str = captured_output.getvalue() # Get any output before error | |
| if simulation_stdout_str: | |
| simulation_stdout_str += f"\n\n{exec_error_msg}" | |
| else: | |
| simulation_stdout_str = exec_error_msg | |
| atoms_object_from_exec = None # Ensure it's None on error | |
| # 3. Plot atoms object if available | |
| if atoms_object_from_exec and isinstance(atoms_object_from_exec, ase.Atoms): | |
| pil_image = plot_atoms_to_pil_image(atoms_object_from_exec) | |
| structure_info_for_analysis = "A molecular structure was generated and visualized." | |
| else: | |
| structure_info_for_analysis = "No molecular structure was available or successfully generated for visualization." | |
| if exec_error_msg and not atoms_object_from_exec : # If exec failed before atoms_object could be assigned | |
| structure_info_for_analysis += f" This might be due to the execution error: {exec_error_msg.splitlines()[0]}" | |
| # 4. Generate analysis using Gemini | |
| try: | |
| prompt_for_analysis = PROMPT_FOR_ANALYSIS.format( | |
| user_query=user_query_str, | |
| generated_code=generated_code_str, | |
| simulation_output=simulation_stdout_str, | |
| structure_info=structure_info_for_analysis | |
| ) | |
| response_analysis = MODEL.generate_content(prompt_for_analysis) | |
| analysis_text_str = response_analysis.text | |
| analysis_text_str = analysis_text_str.strip() | |
| if analysis_text_str.startswith("```markdown"): | |
| analysis_text_str = analysis_text_str[len("```markdown"):].strip() | |
| if analysis_text_str.endswith("```"): | |
| analysis_text_str = analysis_text_str[:-len("```")].strip() | |
| except Exception as e: | |
| analysis_text_str = f"Error generating analysis with Gemini: {traceback.format_exc()}" | |
| if exec_error_msg: # If code exec failed, make analysis reflect that primarily | |
| analysis_text_str = f"Could not perform analysis as the ASE code execution failed.\nDetails: {exec_error_msg}\n\nGemini analysis generation also failed: {e}" | |
| elif not generated_code_str: | |
| analysis_text_str = f"Could not perform analysis as ASE code generation failed.\nGemini analysis generation also failed: {e}" | |
| return generated_code_str, simulation_stdout_str, pil_image, analysis_text_str | |
| # --- Gradio Interface --- | |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="orange")) as demo: | |
| gr.Markdown("# ⚛️ GeminiSim: ASE Simulations via Natural Language 💬") | |
| gr.Markdown( | |
| "Describe your atomic/molecular simulation task in plain English. " | |
| "Gemini AI will generate ASE Python code, run the simulation, and provide an analysis of the results.\n\n" | |
| "**⚠️ SECURITY WARNING:** This tool executes AI-generated Python code using `exec()`. " | |
| "This is inherently unsafe if the AI generates malicious code. Use with caution and only with trusted inputs, " | |
| "especially in non-sandboxed environments." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| user_query_input = gr.Textbox( | |
| label="Your Simulation Request:", | |
| placeholder="e.g., 'Optimize a water molecule (H2O) using EMT and show the final geometry and energy.'", | |
| lines=10, | |
| elem_id="user_query_textbox" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### ✨ Example Prompts:") | |
| with gr.Tabs(): | |
| with gr.TabItem("Simple Molecules"): | |
| gr.Examples( | |
| examples=[ | |
| "Optimize a water molecule (H2O) using EMT and show the final geometry and energy.", | |
| "Optimize a CO molecule using BFGS and show its bond length.", | |
| "Build a methane molecule (CH4) and calculate its energy with EMT.", | |
| "Simulate a single gold (Au) atom.", | |
| "Calculate the binding energy of an O2 molecule." | |
| ], | |
| inputs=[user_query_input], | |
| ) | |
| with gr.TabItem("Complex Simulations"): | |
| gr.Examples( | |
| examples=[ | |
| "Simulate a basic alcohol molecule (e.g., methanol) and report key bond lengths.", | |
| "Relax an ammonia molecule (NH3) and display its bond angles.", | |
| "Optimize a CO2 molecule and display its linearity.", | |
| "Simulate a water dimer and analyze the hydrogen bond length.", | |
| "Create a small gold cluster (Au3) and calculate its binding energy." | |
| ], | |
| inputs=[user_query_input], | |
| ) | |
| submit_button = gr.Button("🚀 Simulate & Analyze", variant="primary") | |
| with gr.Accordion("🔬 Results", open=True): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 🤖 Generated ASE Code") | |
| generated_code_display = gr.Code(language="python", label="Generated ASE Python Code", lines=15, interactive=False) | |
| gr.Markdown("### 🖥️ Simulation Standard Output") | |
| simulation_stdout_display = gr.Textbox(label="Simulation stdout/stderr", lines=8, interactive=False, show_copy_button=True) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 🖼️ Molecular Visualization") | |
| molecule_plot_display = gr.Image(label="Molecular Structure", type="pil", interactive=False, height=350, show_download_button=True) | |
| gr.Markdown("### 🧠 AI Analysis") | |
| ai_analysis_display = gr.Markdown(label="Gemini's Analysis ", show_copy_button=True) | |
| gr.Markdown("---") | |
| gr.Markdown("Built with [Gradio](https://gradio.app/), [ASE](https://wiki.fysik.dtu.dk/ase/), and [Google Gemini](https://ai.google.dev/).") | |
| submit_button.click( | |
| fn=simulate_and_analyze, | |
| inputs=[user_query_input], | |
| outputs=[generated_code_display, simulation_stdout_display, molecule_plot_display, ai_analysis_display] | |
| ) | |
| if __name__ == "__main__": | |
| if not API_KEY or not MODEL: | |
| print("ERROR: Gemini API key not set or model failed to initialize. Gradio server will not start.") | |
| # Optionally, could display an error in Gradio itself if it were to launch. | |
| else: | |
| print("Launching Gradio server...") | |
| demo.launch() # Add share=True if you want a public link (be mindful of security warning!) | |