SKekana commited on
Commit
a0a75f9
·
1 Parent(s): e49bf1a

Redone the entire code.

Browse files
Files changed (2) hide show
  1. app.py +259 -85
  2. requirements.txt +3 -2
app.py CHANGED
@@ -1,102 +1,276 @@
1
  import gradio as gr
2
- from google import genai
3
- from ase import Atoms
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  from ase.calculators.emt import EMT
5
  from ase.optimize import BFGS
6
- import matplotlib.pyplot as plt
7
- import tempfile
8
- import os
9
 
10
- # Gemini API initialization
11
- client = genai.Client(api_key=("AIzaSyCXggYtx5BtkGuXZMaeuhZ9-7xbuf0s7hs"))
12
 
13
- def gemini_prompt(prompt, system="You are a helpful assistant for scientific simulations."):
14
- # model = genai.GenerativeModel(model_name="gemini-pro", system_instruction=system)
15
- # response = model.generate_content(prompt)
16
 
17
- response = client.models.generate_content(
18
- model="gemini-2.0-flash", contents=prompt
19
- )
 
20
 
21
- return response.text
22
-
23
- def parse_params_from_text(text):
24
- # Use Gemini to extract ASE simulation parameters from user text
25
- prompt = f"""Given the following user request, extract the simulation parameters for an ASE simulation as a Python code snippet. The code should define an 'atoms' object using ase.Atoms. Only return the Python code, do not include any explanations or extra text. Make sure all necessary imports are included in the code snippet. If the user asks for a specific calculator, use it; otherwise, use EMT().
26
- Request: {text}"""
27
- code = gemini_prompt(prompt)
28
- return code, prompt
29
-
30
- def run_simulation_from_code(code_str):
31
- # Dangerous: eval! In production, use a safer execution method.
32
- local_vars = {}
33
- exec(code_str, globals(), local_vars)
34
- atoms = local_vars.get('atoms')
35
- if atoms is None:
36
- raise ValueError("No 'atoms' object found in the code.")
37
- dyn = BFGS(atoms, logfile=None)
38
- dyn.run(fmax=0.02)
39
- return atoms
40
-
41
- def analyze_results_with_llm(atoms, user_query):
42
- prompt = f"""Given the following ASE Atoms object representing the simulation results, and the original user request, provide a concise analysis of the simulation. Focus on key properties like bond lengths, angles, and overall structure changes if a geometry optimization was performed. If the user asked for specific information, make sure to include it.
43
- Atoms: {atoms}
44
- User request: {user_query}
45
- Respond with a clear and concise summary."""
46
- summary = gemini_prompt(prompt)
47
- return summary
48
-
49
- def plot_atoms(atoms):
50
- # Save image to temp file
51
- from ase.visualize.plot import plot_atoms
52
- fig, ax = plt.subplots()
53
- plot_atoms(atoms, ax=ax)
54
- tmpfile = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
55
- plt.savefig(tmpfile.name)
56
- plt.close(fig)
57
- return tmpfile.name
58
-
59
- def mcp_tool(user_text):
 
 
 
 
60
  try:
61
- prompt = f"""Given the following user request, extract the simulation parameters for an ASE simulation as a Python code snippet. The code should define an 'atoms' object using ase.Atoms. Only return the Python code, do not include any explanations or extra text. Make sure all necessary imports are included in the code snippet. If the user asks for a specific calculator, use it; otherwise, use EMT().
62
- Request: {user_text}"""
63
- code_str = gemini_prompt(prompt)
64
- atoms = run_simulation_from_code(code_str)
65
- img_path = plot_atoms(atoms)
66
- summary = analyze_results_with_llm(atoms, user_text)
67
- return img_path, summary, prompt, code_str
 
 
 
 
 
68
  except Exception as e:
69
- return None, f"Error: {e}", "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- with gr.Blocks(title="ASE+Gemini MCP Tool") as iface:
72
- gr.Markdown("# ASE+Gemini Molecular Simulation Tool")
73
-
74
  with gr.Row():
75
  with gr.Column(scale=2):
76
- user_prompt = gr.Textbox(
77
- label="Enter your simulation prompt",
78
- placeholder="e.g., 'Optimize a water molecule with DFTB+ calculator'",
79
- lines=3
 
80
  )
81
- submit_btn = gr.Button("Run Simulation")
82
-
83
- with gr.Row():
84
- with gr.Column():
85
- result_image = gr.Image(label="Simulation Result")
86
- with gr.Column():
87
- analysis = gr.Textbox(label="LLM Analysis")
88
-
89
- with gr.Row():
90
- with gr.Column():
91
- prompt_sent = gr.Textbox(label="Prompt Sent to Gemini")
92
- with gr.Column():
93
- generated_code = gr.Code(label="Generated ASE Code")
94
 
95
- submit_btn.click(
96
- fn=mcp_tool,
97
- inputs=user_prompt,
98
- outputs=[result_image, analysis, prompt_sent, generated_code]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  )
100
 
101
  if __name__ == "__main__":
102
- iface.launch()
 
 
 
 
 
 
1
  import gradio as gr
2
+ import os
3
+ from dotenv import load_dotenv
4
+ import google.generativeai as genai
5
+ import ase
6
+ from ase.build import molecule # Often used, good to have for exec scope if needed
7
+ from ase.calculators.emt import EMT # Common default
8
+ from ase.optimize import BFGS # Common optimizer
9
+ from ase.visualize import plot_atoms
10
+ import matplotlib
11
+ matplotlib.use('Agg') # Use Agg backend for non-interactive plotting
12
+ import matplotlib.pyplot as plt
13
+ from PIL import Image
14
+ import io
15
+ import base64
16
+ import traceback
17
+ import contextlib
18
+
19
+ # Load environment variables from .env file (for GEMINI_API_KEY)
20
+ load_dotenv()
21
+
22
+ # --- Gemini API Configuration ---
23
+ API_KEY = "AIzaSyCXggYtx5BtkGuXZMaeuhZ9-7xbuf0s7hs"
24
+ MODEL = None
25
+
26
+ if not API_KEY:
27
+ print("WARNING: GEMINI_API_KEY environment variable not set. App will not function.")
28
+ else:
29
+ try:
30
+ genai.configure(api_key=API_KEY)
31
+ MODEL = genai.GenerativeModel('gemini-2.0-flash') # Or specific model like 'gemini-1.5-flash'
32
+ print("Gemini model initialized successfully.")
33
+ except Exception as e:
34
+ print(f"ERROR: Could not initialize Gemini model: {e}")
35
+ MODEL = None
36
+
37
+ # --- Prompts for Gemini ---
38
+ PROMPT_FOR_ASE_CODE_GENERATION = """
39
+ You are an expert in molecular simulations using the Atomic Simulation Environment (ASE) and Python.
40
+ A user wants to perform the following task: "{user_query}"
41
+
42
+ Generate a complete and runnable Python script using ASE to perform this task.
43
+ The script MUST:
44
+ 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.
45
+ 2. Define the atomic structure.
46
+ 3. Set up an appropriate ASE calculator. If the user doesn't specify, you can use `ase.calculators.emt.EMT()` for simplicity, but acknowledge if a more advanced calculator would be better for real research.
47
+ 4. Perform the simulation as requested (e.g., geometry optimization, molecular dynamics, single point energy calculation).
48
+ 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.
49
+ 6. Optionally, print any relevant information or results from the simulation to standard output (e.g., final energy, bond lengths, status messages).
50
+
51
+ Example of optimizing a water molecule:
52
+ ```python
53
+ import ase
54
+ from ase.build import molecule
55
  from ase.calculators.emt import EMT
56
  from ase.optimize import BFGS
 
 
 
57
 
58
+ # Define the atomic structure
59
+ atoms_object = molecule('H2O')
60
 
61
+ # Set up a calculator
62
+ atoms_object.calc = EMT()
 
63
 
64
+ # Perform the simulation
65
+ print("Starting geometry optimization for H2O...")
66
+ dyn = BFGS(atoms_object)
67
+ dyn.run(fmax=0.05) # Converge when max force is below 0.05 eV/Angstrom
68
 
69
+ # Print results
70
+ print("Optimization finished.")
71
+ print(f"Final energy: {atoms_object.get_potential_energy()} eV")
72
+ # The 'atoms_object' variable now holds the optimized structure.
73
+ ```
74
+
75
+ Provide ONLY the Python code block. Do not include any explanations, comments, or markdown formatting before or after the code block itself.
76
+ The script will be executed in an environment where ASE and its dependencies are installed.
77
+ """
78
+
79
+ PROMPT_FOR_ANALYSIS = """
80
+ You are an AI assistant specialized in interpreting results from molecular simulations performed with the Atomic Simulation Environment (ASE).
81
+
82
+ The user's original simulation request was:
83
+ "{user_query}"
84
+
85
+ The following Python ASE code was generated and executed to address this request:
86
+ ```python
87
+ {generated_code}
88
+ ```
89
+
90
+ The execution of this code produced the following standard output (stdout/stderr):
91
+ ```
92
+ {simulation_output}
93
+ ```
94
+
95
+ {structure_info}
96
+
97
+ Based on all the above information, please provide a concise analysis:
98
+ 1. Briefly explain what the simulation aimed to do and what steps were performed by the code.
99
+ 2. Summarize the key results obtained from the simulation output (e.g., final energy, convergence status, important structural parameters if mentioned in output).
100
+ 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").
101
+ 4. Conclude whether the simulation likely achieved the user's original request.
102
+ 5. If there were any errors or issues, please mention them.
103
+
104
+ Present the analysis in a clear, easy-to-understand manner. Avoid overly technical jargon where possible.
105
+ """
106
+
107
+ # --- Helper Functions ---
108
+ def plot_atoms_to_pil_image(atoms_obj):
109
+ """Converts an ASE Atoms object to a PIL Image for display."""
110
+ if not isinstance(atoms_obj, ase.Atoms):
111
+ return None
112
  try:
113
+ fig, ax = plt.subplots()
114
+ # Use ase.visualize.plot_atoms for plotting
115
+ plot_atoms(atoms_obj, ax, radii=0.3, rotation=('0x,0y,0z'))
116
+ ax.set_xlabel("X (Å)")
117
+ ax.set_ylabel("Y (Å)")
118
+ ax.axis('equal')
119
+
120
+ buf = io.BytesIO()
121
+ fig.savefig(buf, format='png', bbox_inches='tight')
122
+ plt.close(fig) # Close the figure to free up memory
123
+ buf.seek(0)
124
+ return Image.open(buf)
125
  except Exception as e:
126
+ print(f"Error plotting atoms: {traceback.format_exc()}")
127
+ return None
128
+
129
+ # --- Core Simulation Logic ---
130
+ def simulate_and_analyze(user_query_str):
131
+ if not MODEL:
132
+ error_msg = "Gemini model not initialized. Please check API key and configuration."
133
+ return "# Error: Model not initialized.", error_msg, None, error_msg
134
+
135
+ generated_code_str = ""
136
+ simulation_stdout_str = ""
137
+ pil_image = None
138
+ analysis_text_str = ""
139
+
140
+ try:
141
+ # 1. Generate ASE code from user query
142
+ prompt_for_code = PROMPT_FOR_ASE_CODE_GENERATION.format(user_query=user_query_str)
143
+ response_code = MODEL.generate_content(prompt_for_code)
144
+ generated_code_str = response_code.text.strip()
145
+ # Remove potential markdown backticks if Gemini adds them
146
+ if generated_code_str.startswith("```python"):
147
+ generated_code_str = generated_code_str[len("```python"):].strip()
148
+ if generated_code_str.endswith("```"):
149
+ generated_code_str = generated_code_str[:-len("```")].strip()
150
+
151
+ except Exception as e:
152
+ error_msg = f"Error generating ASE code with Gemini: {traceback.format_exc()}"
153
+ return f"# Error: Could not generate ASE code.\n{error_msg}", error_msg, None, error_msg
154
+
155
+ # 2. Execute generated ASE code
156
+ atoms_object_from_exec = None
157
+ execution_namespace = {}
158
+ captured_output = io.StringIO()
159
+ exec_error_msg = None
160
+
161
+ try:
162
+ with contextlib.redirect_stdout(captured_output):
163
+ # Provide a minimal, controlled global scope for exec
164
+ # ASE and its modules should be imported by the generated code itself
165
+ exec(generated_code_str, {'__builtins__': __builtins__, 'ase': ase}, execution_namespace)
166
+
167
+ simulation_stdout_str = captured_output.getvalue()
168
+ atoms_object_from_exec = execution_namespace.get('atoms_object')
169
+
170
+ if atoms_object_from_exec is not None and not isinstance(atoms_object_from_exec, ase.Atoms):
171
+ 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."
172
+ simulation_stdout_str += "\n" + warning_msg
173
+ atoms_object_from_exec = None # Invalidate if not correct type
174
+ elif 'atoms_object' not in execution_namespace:
175
+ simulation_stdout_str += "\nWarning: 'atoms_object' variable not found after code execution. Visualization will not be available."
176
+
177
+ except Exception as e:
178
+ exec_error_msg = f"Error during ASE code execution:\n{traceback.format_exc()}"
179
+ simulation_stdout_str = captured_output.getvalue() # Get any output before error
180
+ if simulation_stdout_str:
181
+ simulation_stdout_str += f"\n\n{exec_error_msg}"
182
+ else:
183
+ simulation_stdout_str = exec_error_msg
184
+ atoms_object_from_exec = None # Ensure it's None on error
185
+
186
+ # 3. Plot atoms object if available
187
+ if atoms_object_from_exec and isinstance(atoms_object_from_exec, ase.Atoms):
188
+ pil_image = plot_atoms_to_pil_image(atoms_object_from_exec)
189
+ structure_info_for_analysis = "A molecular structure was generated and visualized."
190
+ else:
191
+ structure_info_for_analysis = "No molecular structure was available or successfully generated for visualization."
192
+ if exec_error_msg and not atoms_object_from_exec : # If exec failed before atoms_object could be assigned
193
+ structure_info_for_analysis += f" This might be due to the execution error: {exec_error_msg.splitlines()[0]}"
194
+
195
+ # 4. Generate analysis using Gemini
196
+ try:
197
+ prompt_for_analysis = PROMPT_FOR_ANALYSIS.format(
198
+ user_query=user_query_str,
199
+ generated_code=generated_code_str,
200
+ simulation_output=simulation_stdout_str,
201
+ structure_info=structure_info_for_analysis
202
+ )
203
+ response_analysis = MODEL.generate_content(prompt_for_analysis)
204
+ analysis_text_str = response_analysis.text
205
+ except Exception as e:
206
+ analysis_text_str = f"Error generating analysis with Gemini: {traceback.format_exc()}"
207
+ if exec_error_msg: # If code exec failed, make analysis reflect that primarily
208
+ 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}"
209
+ elif not generated_code_str:
210
+ analysis_text_str = f"Could not perform analysis as ASE code generation failed.\nGemini analysis generation also failed: {e}"
211
+
212
+ return generated_code_str, simulation_stdout_str, pil_image, analysis_text_str
213
+
214
+ # --- Gradio Interface ---
215
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="teal", secondary_hue="orange")) as demo:
216
+ gr.Markdown("# ⚛️ GeminiSim: ASE Simulations via Natural Language 💬")
217
+ gr.Markdown(
218
+ "Describe your atomic/molecular simulation task in plain English. "
219
+ "Gemini AI will generate ASE Python code, run the simulation, and provide an analysis of the results.\n\n"
220
+ "**⚠️ SECURITY WARNING:** This tool executes AI-generated Python code using `exec()`. "
221
+ "This is inherently unsafe if the AI generates malicious code. Use with caution and only with trusted inputs, "
222
+ "especially in non-sandboxed environments."
223
+ )
224
 
 
 
 
225
  with gr.Row():
226
  with gr.Column(scale=2):
227
+ user_query_input = gr.Textbox(
228
+ label="Your Simulation Request:",
229
+ placeholder="e.g., 'Optimize a water molecule (H2O) using EMT and show the final geometry and energy.'",
230
+ lines=4,
231
+ elem_id="user_query_textbox"
232
  )
233
+ with gr.Column(scale=1):
234
+ gr.Markdown("### ✨ Example Prompts:")
235
+ gr.Examples(
236
+ examples=[
237
+ "Optimize a CO molecule using BFGS and show its bond length.",
238
+ "Create a 2x2x2 supercell of Silicon (Si) and get its potential energy using EMT.",
239
+ "Build a methane molecule (CH4) and calculate its energy with EMT.",
240
+ "Simulate a single gold (Au) atom."
241
+ ],
242
+ inputs=[user_query_input]
243
+ )
244
+
245
+ submit_button = gr.Button("🚀 Simulate & Analyze", variant="primary")
246
 
247
+ with gr.Accordion("🔬 Results", open=True):
248
+ with gr.Row():
249
+ with gr.Column(scale=1):
250
+ gr.Markdown("### 🤖 Generated ASE Code")
251
+ generated_code_display = gr.Code(language="python", label="Generated ASE Python Code", lines=15, interactive=False)
252
+ gr.Markdown("### 🖥️ Simulation Standard Output")
253
+ simulation_stdout_display = gr.Textbox(label="Simulation stdout/stderr", lines=8, interactive=False, show_copy_button=True)
254
+
255
+ with gr.Column(scale=1):
256
+ gr.Markdown("### 🖼️ Molecular Visualization")
257
+ molecule_plot_display = gr.Image(label="Molecular Structure", type="pil", interactive=False, height=350, show_download_button=True)
258
+ gr.Markdown("### 🧠 AI Analysis")
259
+ ai_analysis_display = gr.Textbox(label="Gemini's Analysis", lines=10, interactive=False, show_copy_button=True)
260
+
261
+ gr.Markdown("---")
262
+ gr.Markdown("Built with [Gradio](https://gradio.app/), [ASE](https://wiki.fysik.dtu.dk/ase/), and [Google Gemini](https://ai.google.dev/).")
263
+
264
+ submit_button.click(
265
+ fn=simulate_and_analyze,
266
+ inputs=[user_query_input],
267
+ outputs=[generated_code_display, simulation_stdout_display, molecule_plot_display, ai_analysis_display]
268
  )
269
 
270
  if __name__ == "__main__":
271
+ if not API_KEY or not MODEL:
272
+ print("ERROR: Gemini API key not set or model failed to initialize. Gradio server will not start.")
273
+ # Optionally, could display an error in Gradio itself if it were to launch.
274
+ else:
275
+ print("Launching Gradio server...")
276
+ demo.launch() # Add share=True if you want a public link (be mindful of security warning!)
requirements.txt CHANGED
@@ -1,6 +1,7 @@
1
  gradio
2
  ase
3
  matplotlib
4
- google-genai
5
  numpy
6
- scipy
 
 
1
  gradio
2
  ase
3
  matplotlib
4
+ google-generativeai
5
  numpy
6
+ scipy
7
+ python-dotenv