初次提交
This commit is contained in:
0
sci_mcp/material_mcp/mp_query/__init__.py
Normal file
0
sci_mcp/material_mcp/mp_query/__init__.py
Normal file
42
sci_mcp/material_mcp/mp_query/get_mp_id.py
Normal file
42
sci_mcp/material_mcp/mp_query/get_mp_id.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import os
|
||||
from typing import List
|
||||
from mp_api.client import MPRester
|
||||
from ...core.config import material_config
|
||||
|
||||
async def get_mpid_from_formula(formula: str) -> List[str]:
|
||||
"""
|
||||
Get material IDs (mpid) from Materials Project database by chemical formula.
|
||||
Returns mpids for the lowest energy structures.
|
||||
|
||||
Args:
|
||||
formula: Chemical formula (e.g., "Fe2O3")
|
||||
|
||||
Returns:
|
||||
List of material IDs
|
||||
"""
|
||||
os.environ['HTTP_PROXY'] = material_config.HTTP_PROXY or ''
|
||||
os.environ['HTTPS_PROXY'] =material_config.HTTPS_PROXY or ''
|
||||
|
||||
|
||||
try:
|
||||
id_list = []
|
||||
|
||||
cleaned_formula = formula.replace(" ", "").replace("\n", "").replace("\'", "").replace("\"", "")
|
||||
if "=" in cleaned_formula:
|
||||
name, id = cleaned_formula.split("=")
|
||||
else:
|
||||
id = cleaned_formula
|
||||
|
||||
formula_list = [id]
|
||||
|
||||
with MPRester(material_config.MP_API_KEY) as mpr:
|
||||
docs = mpr.materials.summary.search(formula=formula_list)
|
||||
if not docs:
|
||||
return "No materials found"
|
||||
else:
|
||||
for doc in docs:
|
||||
id_list.append(doc.material_id)
|
||||
return id_list
|
||||
except Exception as e:
|
||||
|
||||
return f"Error: get_mpid_from_formula: {str(e)}"
|
||||
168
sci_mcp/material_mcp/mp_query/mp_query_tools.py
Normal file
168
sci_mcp/material_mcp/mp_query/mp_query_tools.py
Normal file
@@ -0,0 +1,168 @@
|
||||
|
||||
import glob
|
||||
import json
|
||||
from typing import Dict, Any, Union
|
||||
from ...core.llm_tools import llm_tool
|
||||
from .get_mp_id import get_mpid_from_formula
|
||||
from ..support.utils import extract_cif_info, remove_symmetry_equiv_xyz
|
||||
from ...core.config import material_config
|
||||
|
||||
from pymatgen.core import Structure
|
||||
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
|
||||
from pymatgen.io.cif import CifWriter
|
||||
|
||||
@llm_tool(name="search_crystal_structures_from_materials_project",
|
||||
description="Retrieve and optimize crystal structures from Materials Project database using a chemical formula")
|
||||
async def search_crystal_structures_from_materials_project(
|
||||
formula: str,
|
||||
conventional_unit_cell: bool = True,
|
||||
symprec: float = 0.1
|
||||
) -> str:
|
||||
"""
|
||||
Retrieves crystal structures for a given chemical formula from Materials Project database and applies symmetry optimization.
|
||||
|
||||
Args:
|
||||
formula: Chemical formula to search for (e.g., "Fe2O3")
|
||||
conventional_unit_cell: If True, returns conventional unit cell; if False, returns primitive cell
|
||||
symprec: Symmetry precision parameter for structure refinement (default: 0.1)
|
||||
|
||||
Returns:
|
||||
Formatted CIF data for the retrieved crystal structures with symmetry analysis
|
||||
"""
|
||||
try:
|
||||
structures = {}
|
||||
mp_id_list = await get_mpid_from_formula(formula=formula)
|
||||
if isinstance(mp_id_list, str):
|
||||
return mp_id_list # 直接返回错误信息
|
||||
|
||||
for i, mp_id in enumerate(mp_id_list):
|
||||
try:
|
||||
# 文件操作可能引发异常
|
||||
cif_files = glob.glob(material_config.LOCAL_MP_CIF_ROOT + f"/{mp_id}.cif")
|
||||
if not cif_files:
|
||||
continue # 如果没有找到文件,跳过这个mp_id
|
||||
|
||||
cif_file = cif_files[0]
|
||||
structure = Structure.from_file(cif_file)
|
||||
|
||||
# 结构处理可能引发异常
|
||||
if conventional_unit_cell:
|
||||
structure = SpacegroupAnalyzer(structure).get_conventional_standard_structure()
|
||||
|
||||
# 对结构进行对称化处理
|
||||
sga = SpacegroupAnalyzer(structure, symprec=symprec)
|
||||
symmetrized_structure = sga.get_refined_structure()
|
||||
|
||||
# 使用CifWriter生成CIF数据
|
||||
cif_writer = CifWriter(symmetrized_structure, symprec=symprec, refine_struct=True)
|
||||
cif_data = str(cif_writer)
|
||||
|
||||
# 删除CIF文件中的对称性操作部分
|
||||
cif_data = remove_symmetry_equiv_xyz(cif_data)
|
||||
cif_data = cif_data.replace('# generated using pymatgen', "")
|
||||
|
||||
# 生成一个唯一的键
|
||||
formula_key = structure.composition.reduced_formula
|
||||
key = f"{formula_key}_{i}"
|
||||
|
||||
structures[key] = cif_data
|
||||
|
||||
# 只保留前config.MP_TOPK个结果
|
||||
if len(structures) >= material_config.MP_TOPK:
|
||||
break
|
||||
|
||||
except (FileNotFoundError, IndexError) as file_error:
|
||||
# 处理文件相关错误
|
||||
continue # 跳过这个mp_id,继续处理下一个
|
||||
except ValueError as value_error:
|
||||
# 处理结构处理中的值错误
|
||||
continue # 跳过这个mp_id,继续处理下一个
|
||||
except Exception as process_error:
|
||||
# 记录处理特定结构时的错误,但继续处理其他结构
|
||||
print(f"Error: processing structure {mp_id}: {str(process_error)}")
|
||||
continue
|
||||
|
||||
# 如果没有成功处理任何结构
|
||||
if not structures:
|
||||
return f"No valid crystal structures found for formula: {formula}"
|
||||
|
||||
# 格式化结果为可读字符串
|
||||
prompt = f"""
|
||||
# Materials Project Symmetrized Crystal Structure Data
|
||||
|
||||
Below are symmetrized crystal structure data for {len(structures)} materials from the Materials Project database, in CIF (Crystallographic Information File) format.
|
||||
These structures have been analyzed and optimized for symmetry using SpacegroupAnalyzer with precision parameter symprec={symprec}.\n
|
||||
"""
|
||||
|
||||
for i, (key, cif_data) in enumerate(structures.items(), 1):
|
||||
prompt += f"[cif {i} begin]\n"
|
||||
prompt += cif_data
|
||||
prompt += f"\n[cif {i} end]\n\n"
|
||||
|
||||
return prompt
|
||||
|
||||
except Exception as e:
|
||||
# 捕获整个函数执行过程中的任何未处理异常
|
||||
return f"Error: An unexpected error occurred while processing crystal structures: {str(e)}"
|
||||
|
||||
@llm_tool(name="search_material_property_from_material_project",
|
||||
description="Query material properties from Materials Project database using chemical formula")
|
||||
async def search_material_property_from_materials_project(
|
||||
formula: str,
|
||||
) -> str:
|
||||
"""
|
||||
Retrieve detailed property data for materials matching a chemical formula from Materials Project database.
|
||||
|
||||
Args:
|
||||
formula: Chemical formula of the material(s) to search for (e.g. 'Fe2O3', 'LiFePO4')
|
||||
|
||||
Returns:
|
||||
Formatted string containing material properties including structure, electronic, thermodynamic and mechanical data
|
||||
"""
|
||||
# 获取MP ID列表
|
||||
mp_id_list = await get_mpid_from_formula(formula=formula)
|
||||
|
||||
# 检查get_mpid_from_formula的返回值类型
|
||||
# 如果返回的是字符串,说明发生了错误或没有找到材料
|
||||
if isinstance(mp_id_list, str):
|
||||
return mp_id_list # 直接返回错误信息
|
||||
|
||||
# 如果代码执行到这里,说明mp_id_list是一个有效的ID列表
|
||||
try:
|
||||
# 获取材料属性
|
||||
properties = []
|
||||
for mp_id in mp_id_list:
|
||||
try:
|
||||
file_path = material_config.LOCAL_MP_PROPS_ROOT + f"/{mp_id}.json"
|
||||
crystal_props = extract_cif_info(file_path, ['all_fields'])
|
||||
properties.append(crystal_props)
|
||||
except Exception as file_error:
|
||||
# 记录单个文件处理错误但继续处理其他ID
|
||||
continue
|
||||
|
||||
# 检查是否有结果
|
||||
if len(properties) == 0:
|
||||
return "No material properties found for the given formula, please try again."
|
||||
|
||||
# 只保留前MP_TOPK个结果
|
||||
properties = properties[:material_config.MP_TOPK]
|
||||
|
||||
# 格式化结果
|
||||
formatted_results = []
|
||||
for i, item in enumerate(properties, 1):
|
||||
formatted_result = f"[property {i} begin]\n"
|
||||
formatted_result += json.dumps(item, indent=2)
|
||||
formatted_result += f"\n[property {i} end]\n\n"
|
||||
formatted_results.append(formatted_result)
|
||||
|
||||
# 将所有结果合并为一个字符串
|
||||
res_chunk = "\n\n".join(formatted_results)
|
||||
res_template = f"""
|
||||
Here are the search material property from the Materials Project database:
|
||||
Due to length limitations, only the top {len(properties)} results are shown below:\n
|
||||
{res_chunk}
|
||||
"""
|
||||
return res_template
|
||||
|
||||
except Exception as e:
|
||||
return f"Error: processing material properties: {str(e)}"
|
||||
Reference in New Issue
Block a user