File size: 13,919 Bytes
294266d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65479a9
 
294266d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65479a9
294266d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
972f03f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
import os
import json
import gradio as gr
import groq
import numpy as np
import matplotlib.pyplot as plt
import py3Dmol
from pymatgen.core.structure import Structure
from pymatgen.io.cif import CifWriter
from pymatgen.io.xyz import XYZ
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
import requests
from dotenv import load_dotenv
import tempfile
import base64

# Load environment variables
load_dotenv()

# Retrieve the API key from the environment variable
groq_api_key = os.getenv("GROQ_API_KEY")
if not groq_api_key:
    raise ValueError("GROQ_API_KEY environment variable not set")

# Initialize Groq client - using a simpler initialization to avoid proxies parameter issue
client = groq.Client(api_key=groq_api_key)

# Function to generate crystal structures
def generate_crystal_structure(material_name):
    """Generate a crystal structure for a given material."""
    # This is a simplified version - in a real application, you would use a database or API
    # to get real crystal structure data for materials
    
    # Dictionary of common materials and their crystal structures
    material_structures = {
        "Silicon": {
            "spacegroup": "Fd-3m",
            "lattice": [[0, 5.431/2, 5.431/2], [5.431/2, 0, 5.431/2], [5.431/2, 5.431/2, 0]],
            "species": ["Si"],
            "coords": [[0, 0, 0]]
        },
        "Titanium Dioxide": {
            "spacegroup": "P42/mnm",
            "lattice": [[4.594, 0, 0], [0, 4.594, 0], [0, 0, 2.959]],
            "species": ["Ti", "O", "O"],
            "coords": [[0, 0, 0], [0.3053, 0.3053, 0], [0.3053, 0.6947, 0.5]]
        },
        "Graphene": {
            "spacegroup": "P6/mmm",
            "lattice": [[2.46, 0, 0], [-2.46/2, 2.46*np.sqrt(3)/2, 0], [0, 0, 15]],
            "species": ["C", "C"],
            "coords": [[0, 0, 0], [1/3, 2/3, 0]]
        },
        "Copper": {
            "spacegroup": "Fm-3m",
            "lattice": [[3.615, 0, 0], [0, 3.615, 0], [0, 0, 3.615]],
            "species": ["Cu"],
            "coords": [[0, 0, 0]]
        },
        "Aluminum": {
            "spacegroup": "Fm-3m",
            "lattice": [[4.05, 0, 0], [0, 4.05, 0], [0, 0, 4.05]],
            "species": ["Al"],
            "coords": [[0, 0, 0]]
        },
        "Gold": {
            "spacegroup": "Fm-3m",
            "lattice": [[4.078, 0, 0], [0, 4.078, 0], [0, 0, 4.078]],
            "species": ["Au"],
            "coords": [[0, 0, 0]]
        },
        "Diamond": {
            "spacegroup": "Fd-3m",
            "lattice": [[0, 3.567/2, 3.567/2], [3.567/2, 0, 3.567/2], [3.567/2, 3.567/2, 0]],
            "species": ["C"],
            "coords": [[0, 0, 0]]
        },
        "Graphite": {
            "spacegroup": "P63/mmc",
            "lattice": [[2.46, 0, 0], [-1.23, 2.13, 0], [0, 0, 6.71]],
            "species": ["C", "C", "C", "C"],
            "coords": [[0, 0, 0], [0, 0, 0.5], [1/3, 2/3, 0], [2/3, 1/3, 0.5]]
        }
    }
    
    # Try to match the material name with our database (case insensitive)
    for known_material, structure_data in material_structures.items():
        if material_name.lower() in known_material.lower():
            structure = Structure.from_spacegroup(
                structure_data["spacegroup"],
                lattice=structure_data["lattice"],
                species=structure_data["species"],
                coords=structure_data["coords"]
            )
            return structure
    
    # If material not found, create a generic structure
    return Structure.from_spacegroup(
        "Pm-3m",
        lattice=[[4.0, 0, 0], [0, 4.0, 0], [0, 0, 4.0]],
        species=["X"],
        coords=[[0, 0, 0]]
    )

# Function to get material recommendations from LLM
def get_material_recommendations(query):
    """Get material recommendations from the LLM based on user query."""
    system_prompt = """You are a materials science expert. Your task is to recommend the 3 best materials for a specific application or with certain properties based on the user's query.
    
    For each material, provide:
    1. Material name
    2. Chemical formula
    3. Key properties relevant to the application
    4. Why it's suitable for the application
    5. Any limitations or considerations
    
    Format your response as a JSON object with the following structure:
    {
        "materials": [
            {
                "name": "Material Name",
                "formula": "Chemical Formula",
                "properties": "Key properties relevant to the application",
                "suitability": "Why it's suitable for the application",
                "limitations": "Any limitations or considerations"
            },
            // Second material
            // Third material
        ]
    }
    
    Ensure your response is strictly in this JSON format with no additional text."""
    
    try:
        # Updated to use the chat.completions API with the correct client method
        completion = client.chat.completions.create(
            model="deepseek-r1",
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": query}
            ],
            temperature=0.2,
            max_tokens=1000
        )
        
        response_text = completion.choices[0].message.content
        
        # Extract JSON from the response
        try:
            # Try to parse the entire response as JSON
            recommendations = json.loads(response_text)
        except json.JSONDecodeError:
            # If that fails, try to extract JSON using string manipulation
            json_start = response_text.find('{')
            json_end = response_text.rfind('}') + 1
            if json_start >= 0 and json_end > json_start:
                json_str = response_text[json_start:json_end]
                recommendations = json.loads(json_str)
            else:
                raise ValueError("Could not extract valid JSON from LLM response")
        
        return recommendations
    except Exception as e:
        return {"error": str(e)}

# Function to get crystal structure information
def get_crystal_structure_info(material_name):
    """Get crystal structure information for a given material."""
    try:
        # Generate the crystal structure
        structure = generate_crystal_structure(material_name)
        
        # Create a CIF file
        cif_writer = CifWriter(structure)
        with tempfile.NamedTemporaryFile(suffix='.cif', delete=False) as temp_cif:
            cif_writer.write_file(temp_cif.name)
            cif_path = temp_cif.name
        
        with open(cif_path, 'r') as f:
            cif_content = f.read()
        
        # Create an XYZ file
        with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as temp_xyz:
            xyz_path = temp_xyz.name
        
        # Convert structure to XYZ format
        ase_atoms = structure.to_ase()
        from ase.io import write as ase_write
        ase_write(xyz_path, ase_atoms, format='xyz')
        
        with open(xyz_path, 'r') as f:
            xyz_content = f.read()
        
        # Get space group information
        analyzer = SpacegroupAnalyzer(structure)
        spacegroup = analyzer.get_space_group_symbol()
        
        # Generate 3D visualization
        view = py3Dmol.view(width=500, height=400)
        view.addModel(xyz_content, 'xyz')
        view.setStyle({'sphere': {'colorscheme': 'Jmol', 'scale': 0.3},
                       'stick': {'radius': 0.2}})
        view.zoomTo()
        view.spin(True)
        view.setBackgroundColor('white')
        view.render()
        
        # Convert the view to HTML
        html_str = view._make_html()
        
        # Clean up temporary files
        os.unlink(cif_path)
        os.unlink(xyz_path)
        
        return {
            "material_name": material_name,
            "formula": structure.composition.reduced_formula,
            "space_group": spacegroup,
            "num_atoms": len(structure),
            "lattice_parameters": {
                "a": structure.lattice.a,
                "b": structure.lattice.b,
                "c": structure.lattice.c,
                "alpha": structure.lattice.alpha,
                "beta": structure.lattice.beta,
                "gamma": structure.lattice.gamma
            },
            "cif_content": cif_content,
            "xyz_content": xyz_content,
            "visualization_html": html_str
        }
    except Exception as e:
        return {"error": str(e)}

# Function to create a downloadable file
def create_downloadable_file(content, filename):
    """Create a downloadable file with the given content."""
    with open(filename, 'w') as f:
        f.write(content)
    return filename

# Gradio interface
def process_query(query):
    """Process the user query and return material recommendations and crystal structure visualization."""
    try:
        # Get material recommendations from LLM
        recommendations = get_material_recommendations(query)
        
        if "error" in recommendations:
            return f"Error getting recommendations: {recommendations['error']}", None, None, None
        
        # Format the recommendations as text
        recommendation_text = "# Material Recommendations\n\n"
        
        for i, material in enumerate(recommendations.get("materials", [])):
            recommendation_text += f"## {i+1}. {material.get('name', 'Unknown')}\n"
            recommendation_text += f"**Formula:** {material.get('formula', 'N/A')}\n\n"
            recommendation_text += f"**Properties:** {material.get('properties', 'N/A')}\n\n"
            recommendation_text += f"**Suitability:** {material.get('suitability', 'N/A')}\n\n"
            recommendation_text += f"**Limitations:** {material.get('limitations', 'N/A')}\n\n"
        
        # Get crystal structure information for the first recommended material
        cif_file = None
        xyz_file = None
        
        if recommendations.get("materials") and len(recommendations.get("materials")) > 0:
            first_material = recommendations["materials"][0]["name"]
            structure_info = get_crystal_structure_info(first_material)
            
            if "error" in structure_info:
                structure_html = f"<p>Error getting crystal structure: {structure_info['error']}</p>"
            else:
                # Add crystal structure information to the recommendation text
                recommendation_text += f"# Crystal Structure of {structure_info['material_name']}\n\n"
                recommendation_text += f"**Formula:** {structure_info['formula']}\n\n"
                recommendation_text += f"**Space Group:** {structure_info['space_group']}\n\n"
                recommendation_text += f"**Number of Atoms:** {structure_info['num_atoms']}\n\n"
                recommendation_text += "**Lattice Parameters:**\n"
                recommendation_text += f"a = {structure_info['lattice_parameters']['a']:.4f} Å, "
                recommendation_text += f"b = {structure_info['lattice_parameters']['b']:.4f} Å, "
                recommendation_text += f"c = {structure_info['lattice_parameters']['c']:.4f} Å\n"
                recommendation_text += f"α = {structure_info['lattice_parameters']['alpha']:.2f}°, "
                recommendation_text += f"β = {structure_info['lattice_parameters']['beta']:.2f}°, "
                recommendation_text += f"γ = {structure_info['lattice_parameters']['gamma']:.2f}°\n\n"
                
                # Create HTML for the 3D visualization
                structure_html = structure_info['visualization_html']
                
                # Create downloadable files
                cif_file = create_downloadable_file(structure_info['cif_content'], f"{structure_info['material_name'].replace(' ', '_')}.cif")
                xyz_file = create_downloadable_file(structure_info['xyz_content'], f"{structure_info['material_name'].replace(' ', '_')}.xyz")
        else:
            structure_html = "<p>No materials recommended to visualize.</p>"
        
        return recommendation_text, structure_html, cif_file, xyz_file
    except Exception as e:
        return f"Error processing query: {str(e)}", None, None, None

# Create the Gradio interface
with gr.Blocks(title="Material Science Expert") as demo:
    gr.Markdown("# Material Science Expert")
    gr.Markdown("Ask for a material for a specific application or with certain properties.")
    
    with gr.Row():
        with gr.Column():
            query_input = gr.Textbox(
                label="Your Query",
                placeholder="I need a material with high thermal conductivity for electronics cooling.",
                lines=3
            )
            submit_btn = gr.Button("Get Recommendations")
        
    with gr.Row():
        with gr.Column(scale=1):
            recommendations_output = gr.Markdown(label="Material Recommendations")
        with gr.Column(scale=1):
            structure_output = gr.HTML(label="Crystal Structure Visualization")
    
    with gr.Row():
        with gr.Column():
            cif_file_output = gr.File(label="Download CIF File")
            xyz_file_output = gr.File(label="Download XYZ File")
    
    submit_btn.click(
        fn=process_query,
        inputs=[query_input],
        outputs=[recommendations_output, structure_output, cif_file_output, xyz_file_output]
    )
    
    gr.Markdown("""
    ## Example Queries:
    - I need a material with high thermal conductivity for electronics cooling.
    - What are the best materials for solar cell applications?
    - Recommend materials with high strength-to-weight ratio for aerospace applications.
    - I need a transparent conductive material for touchscreens.
    """)

# Launch the app
if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0")