From 7034566ee6858641f167d39e5f21b44c3e667761 Mon Sep 17 00:00:00 2001 From: lzy <949777411@qq.com> Date: Wed, 2 Apr 2025 16:24:50 +0800 Subject: [PATCH] =?UTF-8?q?mattergen=E8=BD=AC=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mars_toolkit/__init__.py | 22 +- .../__pycache__/__init__.cpython-310.pyc | Bin 1520 -> 1115 bytes .../__pycache__/material_gen.cpython-310.pyc | Bin 12656 -> 9195 bytes .../__pycache__/structure_opt.cpython-310.pyc | Bin 5306 -> 5229 bytes mars_toolkit/compute/material_gen.py | 157 +------- mars_toolkit/compute/structure_opt.py | 3 +- mars_toolkit/core/__init__.py | 5 - .../core/__pycache__/__init__.cpython-310.pyc | Bin 608 -> 360 bytes .../__pycache__/cif_utils.cpython-310.pyc | Bin 3465 -> 3394 bytes .../core/__pycache__/config.cpython-310.pyc | Bin 2060 -> 2060 bytes mars_toolkit/core/cif_utils.py | 4 - mars_toolkit/core/error_handlers.py | 55 --- mars_toolkit/core/utils.py | 75 ---- .../__pycache__/dify_search.cpython-310.pyc | Bin 1956 -> 1721 bytes .../__pycache__/mp_query.cpython-310.pyc | Bin 13268 -> 12944 bytes .../__pycache__/oqmd_query.cpython-310.pyc | Bin 3199 -> 3006 bytes mars_toolkit/query/dify_search.py | 9 - mars_toolkit/query/mp_query.py | 12 - mars_toolkit/query/oqmd_query.py | 10 - mars_toolkit/services/__init__.py | 12 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 458 bytes .../mattergen_service.cpython-310.pyc | Bin 0 -> 9776 bytes mars_toolkit/services/mattergen_service.py | 342 ++++++++++++++++++ mars_toolkit/visualization/__init__.py | 2 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 578 bytes .../__pycache__/band_vis.cpython-310.pyc | Bin 0 -> 10223 bytes .../__pycache__/crystal_vis.cpython-310.pyc | Bin 0 -> 10445 bytes mattergen_api.py | 151 ++++++++ mattergen_client_example.py | 134 +++++++ test_mars_toolkit.py | 2 +- 30 files changed, 656 insertions(+), 339 deletions(-) delete mode 100644 mars_toolkit/core/error_handlers.py delete mode 100644 mars_toolkit/core/utils.py create mode 100644 mars_toolkit/services/__init__.py create mode 100644 mars_toolkit/services/__pycache__/__init__.cpython-310.pyc create mode 100644 mars_toolkit/services/__pycache__/mattergen_service.cpython-310.pyc create mode 100644 mars_toolkit/services/mattergen_service.py create mode 100644 mars_toolkit/visualization/__pycache__/__init__.cpython-310.pyc create mode 100644 mars_toolkit/visualization/__pycache__/band_vis.cpython-310.pyc create mode 100644 mars_toolkit/visualization/__pycache__/crystal_vis.cpython-310.pyc create mode 100644 mattergen_api.py create mode 100644 mattergen_client_example.py diff --git a/mars_toolkit/__init__.py b/mars_toolkit/__init__.py index fba5167..8c760db 100644 --- a/mars_toolkit/__init__.py +++ b/mars_toolkit/__init__.py @@ -1,21 +1,7 @@ -""" -Mars Toolkit - -A comprehensive toolkit for materials science research, providing tools for: -- Material generation and property prediction -- Structure optimization -- Database queries (Materials Project, OQMD) -- Knowledge base retrieval -- Web search - -Author: Yutang LI -Institution: SIAT-MIC -Contact: yt.li2@siat.ac.cn -""" # Core modules from mars_toolkit.core.config import config -from mars_toolkit.core.utils import setup_logging + # Basic tools from mars_toolkit.misc.misc_tools import get_current_time @@ -35,12 +21,14 @@ from mars_toolkit.query.oqmd_query import fetch_chemical_composition_from_OQMD from mars_toolkit.query.dify_search import retrieval_from_knowledge_base from mars_toolkit.query.web_search import search_online +# Visualization modules + + from mars_toolkit.core.llm_tools import llm_tool, get_tools, get_tool_schemas -# Initialize logging -setup_logging() + __version__ = "0.1.0" __all__ = ["llm_tool", "get_tools", "get_tool_schemas"] diff --git a/mars_toolkit/__pycache__/__init__.cpython-310.pyc b/mars_toolkit/__pycache__/__init__.cpython-310.pyc index 27f48c35e03ace427dc21d269fbbb2ea20558f91..e4e221594f3111f558246b768de91e493cf1bf49 100644 GIT binary patch delta 361 zcmW;Gy-ve05C`yFZ0FNSLkVpuEutzCbYNiN0Xnj>-C_`ds*-T)kX`0ICfKIwkbk0`IREb;7{etys1*nK{@eXv#KRiHfr1ZeDQpBOS{ zra1`|&Rho+V(2V%L@~ycVB+dPrzBBQh8g9UyI!bURAAv8={0I&+d0lhCZT|mNg+W(DR>~^A5~FErT_o{ delta 773 zcmXw%KX2497{=`+_oun!?n+yZ{-JUU60OwUz<6S4)h-8|C~75y2O>9dQ-d8JwsWW~ zH}nG_lyAU-7(sj!wqt-5iGhV3C$x!V{p{y`?8mSCz4@ormSN}v`+fWRJKd|jZ?8?i z*jWNFNMPb{C>-p-l3nf!4|^4Rye8_nUU7{#L=!hFuJbwJW541CZ;1d0BE(@eZ}PV2 z;Ew3xu9(O3)!H1tAr|mL#XesYOLz%R%fnjyrs&~bB?SDISjNj@1+R!zyb1yJE`e1- zyq1C__yF%3y^2^~QRNj+A^!W3z*vJRKcw+v-P|e8Xvy zQdD^qHP$mB+)B^rDblozZ10+zbDI)9&keD;9}oRurVTTBnPz~Fhx;eHqv3&nATvW^ zGe8%n&)NMw&4}rfxF2W!F96m6*oL!uEVDC~S_(Ba`8eV-OvYY(lSi=iDoPIIErO9z|u3RK57=Y}Fg~v8iU!b~jpc zC6;edwTgjz2L{Cp_bv>JOUv!zqr0)&DaCF%%-dnsilPZs+FmY-%1DIsDEb5xDl1U& f*L}FTZqWllH-Ld_f7peKZcwu`xaibto)f@-@O0QG diff --git a/mars_toolkit/compute/__pycache__/material_gen.cpython-310.pyc b/mars_toolkit/compute/__pycache__/material_gen.cpython-310.pyc index 25707b7bdeea5c0fb8d3f438a3b163b2f039390d..319fd14e4604b64ca516f810e4df6e3dd314ec6c 100644 GIT binary patch delta 1377 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 delta 1018 zcmZuvOHUI~6z&~L+bImvGV<<=f^>M4hZ|G`34ugI2!zL?5+{@1+uAVgkkhFo#sm}D zy1<+0LKnJZV@xJt!NP>N^dHP`(1i<^Cf;)~f`&=v%RT3Q=iKjp_k7Qt8k(bN(SZ0C z&%Twv9BB!%b2R$(fD(Gm&Qu2q10=x0@3U<111Stq#kxV;Y3wji7^ZQSqaEVcnacE@ zKq*A8)6T3zMbj@8HKS%|R(8X# zeXI-H=1LJdK$C~fFGyjGDjNY~(0fkiNVh?J{jpV}HZ#kt#=x>o2H=4AS(zpz&VMv5 zwZz4he8P?ckl+i!O_JfSgDH~oPJ(xo{vNRjT?oAheFBUSq;Z|$S~xY_kITegNQ#vo zD>}6&3|zDBLZj$5fKBj?aF$GY`{6l4hWJTjWyZjo5d;luqWe&BnOUmX3`Q{#tTVO? zAta0;j0+G)PxISqCX+`-v{)&bvJ4=DluI)BGqsm2^EYZDG>co4-l;mFv}SRAQ@}C| zc|TjCgy^p{drMN}iEbEV#?hw4DX1ORCjM4pc4I z1Xgd@E?nhX+}U$0)yh+LVcbLhsBN9B@lS0V9nzK-%IZa^2%=Mid9U-@s`6k;oaaZk zExW;%@O5ELBn%>T95VS7$O+se%LgrQ#4K37O}5X98hrg9)dJ|%zGQpl7n>B zg1YHCj=fuP^F;?(UQVoeKE_Z7l;f*q=B_y9G6O$inl-D+L~A*x=jyR74lAHHAm0~F ek>W1R$%Pk@*{VYuc7)9l!K<(Gq(00S^^xDzyzY+x diff --git a/mars_toolkit/compute/material_gen.py b/mars_toolkit/compute/material_gen.py index 530790c..ac60b4a 100644 --- a/mars_toolkit/compute/material_gen.py +++ b/mars_toolkit/compute/material_gen.py @@ -1,12 +1,3 @@ -""" -Material Generation Module - -This module provides functions for generating crystal structures with optional property constraints. - -Author: Yutang LI -Institution: SIAT-MIC -Contact: yt.li2@siat.ac.cn -""" import ast import json @@ -276,148 +267,16 @@ async def generate_material( Returns: Descriptive text with generated crystal structures in CIF format """ - # 使用配置中的结果目录 - output_dir = config.MATTERGENMODEL_RESULT_PATH + # 导入MatterGenService + from mars_toolkit.services.mattergen_service import MatterGenService - # 处理字符串输入(如果提供) - if isinstance(properties, str): - try: - properties = json.loads(properties) - except json.JSONDecodeError: - raise ValueError(f"Invalid properties JSON string: {properties}") + # 获取MatterGenService实例 + service = MatterGenService.get_instance() - # 如果为None,默认为空字典 - properties = properties or {} - - # 根据生成模式处理属性 - if not properties: - # 无条件生成 - model_path = os.path.join(config.MATTERGENMODEL_ROOT, "mattergen_base") - properties_to_condition_on = None - generation_type = "unconditional" - property_description = "unconditionally" - else: - # 条件生成(单属性或多属性) - properties_to_condition_on = {} - - # 处理每个属性 - for property_name, property_value in properties.items(): - _, processed_value = preprocess_property(property_name, property_value) - properties_to_condition_on[property_name] = processed_value - - # 根据属性确定使用哪个模型 - if len(properties) == 1: - # 单属性条件 - property_name = list(properties.keys())[0] - property_to_model = { - "dft_mag_density": "dft_mag_density", - "dft_bulk_modulus": "dft_bulk_modulus", - "dft_shear_modulus": "dft_shear_modulus", - "energy_above_hull": "energy_above_hull", - "formation_energy_per_atom": "formation_energy_per_atom", - "space_group": "space_group", - "hhi_score": "hhi_score", - "ml_bulk_modulus": "ml_bulk_modulus", - "chemical_system": "chemical_system", - "dft_band_gap": "dft_band_gap" - } - model_dir = property_to_model.get(property_name, property_name) - generation_type = "single_property" - property_description = f"conditioned on {property_name} = {properties[property_name]}" - else: - # 多属性条件 - property_keys = set(properties.keys()) - if property_keys == {"dft_mag_density", "hhi_score"}: - model_dir = "dft_mag_density_hhi_score" - elif property_keys == {"chemical_system", "energy_above_hull"}: - model_dir = "chemical_system_energy_above_hull" - else: - # 如果没有特定的多属性模型,使用第一个属性的模型 - first_property = list(properties.keys())[0] - model_dir = first_property - generation_type = "multi_property" - property_description = f"conditioned on multiple properties: {', '.join([f'{name} = {value}' for name, value in properties.items()])}" - - # 构建完整的模型路径 - model_path = os.path.join(config.MATTERGENMODEL_ROOT, model_dir) - - # 检查模型目录是否存在 - if not os.path.exists(model_path): - # 如果特定模型不存在,回退到基础模型 - logger.warning(f"Model directory for {model_dir} not found. Using base model instead.") - model_path = os.path.join(config.MATTERGENMODEL_ROOT, "mattergen_base") - - # 使用适当的参数调用main函数 - main( - output_path=output_dir, - model_path=model_path, + # 使用服务生成材料 + return service.generate( + properties=properties, batch_size=batch_size, num_batches=num_batches, - properties_to_condition_on=properties_to_condition_on, - record_trajectories=True, - diffusion_guidance_factor=diffusion_guidance_factor if properties else 0.0 + diffusion_guidance_factor=diffusion_guidance_factor ) - - # 创建字典存储文件内容 - result_dict = {} - - # 定义文件路径 - cif_zip_path = os.path.join(output_dir, "generated_crystals_cif.zip") - xyz_file_path = os.path.join(output_dir, "generated_crystals.extxyz") - trajectories_zip_path = os.path.join(output_dir, "generated_trajectories.zip") - - # 读取CIF压缩文件 - if os.path.exists(cif_zip_path): - with open(cif_zip_path, 'rb') as f: - result_dict['cif_content'] = f.read() - - # 根据生成类型创建描述性提示 - if generation_type == "unconditional": - title = "Generated Material Structures" - description = "These structures were generated unconditionally, meaning no specific properties were targeted." - elif generation_type == "single_property": - property_name = list(properties.keys())[0] - property_value = properties[property_name] - title = f"Generated Material Structures Conditioned on {property_name} = {property_value}" - description = f"These structures were generated with property conditioning, targeting a {property_name} value of {property_value}." - else: # multi_property - title = "Generated Material Structures Conditioned on Multiple Properties" - description = "These structures were generated with multi-property conditioning, targeting the specified property values." - - # 创建完整的提示 - prompt = f""" -# {title} - -This data contains {batch_size * num_batches} crystal structures generated by the MatterGen model, {property_description}. - - {'' if generation_type == 'unconditional' else f''' - A diffusion guidance factor of {diffusion_guidance_factor} was used, which controls how strongly - the generation adheres to the specified property values. Higher values produce samples that more - closely match the target properties but may reduce diversity. - '''} - -## CIF Files (Crystallographic Information Files) - -- Standard format for crystallographic structures -- Contains unit cell parameters, atomic positions, and symmetry information -- Used by crystallographic software and visualization tools - -``` -{format_cif_content(result_dict.get('cif_content', b'').decode('utf-8', errors='replace') if isinstance(result_dict.get('cif_content', b''), bytes) else str(result_dict.get('cif_content', '')))} -``` - -{description} -You can use these structures for materials discovery, property prediction, or further analysis. -""" - - # 清理文件(读取后删除) - try: - if os.path.exists(cif_zip_path): - os.remove(cif_zip_path) - if os.path.exists(xyz_file_path): - os.remove(xyz_file_path) - if os.path.exists(trajectories_zip_path): - os.remove(trajectories_zip_path) - except Exception as e: - logger.warning(f"Error cleaning up files: {e}") - return prompt diff --git a/mars_toolkit/compute/structure_opt.py b/mars_toolkit/compute/structure_opt.py index 9fed655..132fbf8 100644 --- a/mars_toolkit/compute/structure_opt.py +++ b/mars_toolkit/compute/structure_opt.py @@ -23,7 +23,6 @@ from pymatgen.io.cif import CifWriter from mars_toolkit.core.cif_utils import remove_symmetry_equiv_xyz from mars_toolkit.core.llm_tools import llm_tool from mars_toolkit.core.config import config -from mars_toolkit.core.error_handlers import handle_general_error logger = logging.getLogger(__name__) @@ -189,4 +188,4 @@ async def optimize_crystal_structure( # 直接返回结果或抛出异常 return await asyncio.to_thread(run_optimization) except Exception as e: - return handle_general_error(e) + return str(e) diff --git a/mars_toolkit/core/__init__.py b/mars_toolkit/core/__init__.py index 771eee2..ba4e1fc 100644 --- a/mars_toolkit/core/__init__.py +++ b/mars_toolkit/core/__init__.py @@ -5,9 +5,4 @@ This module provides core functionality for the Mars Toolkit. """ from mars_toolkit.core.config import config -from mars_toolkit.core.utils import settings, setup_logging -from mars_toolkit.core.error_handlers import ( - handle_minio_error, handle_http_error, - handle_validation_error, handle_general_error -) from mars_toolkit.core.llm_tools import llm_tool diff --git a/mars_toolkit/core/__pycache__/__init__.cpython-310.pyc b/mars_toolkit/core/__pycache__/__init__.cpython-310.pyc index 8aa07d2c4b150b27d735c5ab4fd300a3811f3dec..12e8a116cc40be19c2247762e575c792de9c88b0 100644 GIT binary patch delta 91 zcmaFB@`8ykpO=@50SFB5zDeK6Fp*D&QDLICEC(Y)3QI79ChNqCI6lT(9633;@g@2B qIewa~lfxM`C*Ne0XQPfABig33nRD9>le%eKUJ`q}uG(61rbH_=;{Y1$ z(ZtrqY$i0H&~qh4T^XUki|9JniiII$Q1TgHXheg<^l552)4;UH1}bD71?y(1_FP`nlRHXj~GP#VneDV~uX Dxt?Zn diff --git a/mars_toolkit/core/__pycache__/cif_utils.cpython-310.pyc b/mars_toolkit/core/__pycache__/cif_utils.cpython-310.pyc index a7c6fcbfbedd8b0741862e2b7239dad6df8c3893..ed92f14d781d236ba6e01a78945cf6e04196c244 100644 GIT binary patch delta 70 zcmeB_J|xAP&&$ij00c5O-=yk)hW%J&&$ij00gdE-=qgk$`=jG*M00No28@ZS`045~^lmGw# delta 19 YcmeAX=n>$`=jG*M0D|ys8@ZS`04Br(r~m)} diff --git a/mars_toolkit/core/cif_utils.py b/mars_toolkit/core/cif_utils.py index 7fa40ae..044f0a6 100644 --- a/mars_toolkit/core/cif_utils.py +++ b/mars_toolkit/core/cif_utils.py @@ -3,10 +3,6 @@ CIF Utilities Module This module provides basic functions for handling CIF (Crystallographic Information File) files, which are commonly used in materials science for representing crystal structures. - -Author: Yutang LI -Institution: SIAT-MIC -Contact: yt.li2@siat.ac.cn """ import json diff --git a/mars_toolkit/core/error_handlers.py b/mars_toolkit/core/error_handlers.py deleted file mode 100644 index 64e6484..0000000 --- a/mars_toolkit/core/error_handlers.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Error Handlers Module - -This module provides error handling utilities for the Mars Toolkit. -It includes functions for handling various types of errors that may occur -during toolkit operations. - -Author: Yutang LI -Institution: SIAT-MIC -Contact: yt.li2@siat.ac.cn -""" - -from fastapi import HTTPException -from typing import Any, Dict -import logging - -logger = logging.getLogger(__name__) - -class APIError(HTTPException): - """自定义API错误类""" - def __init__(self, status_code: int, detail: Any = None): - super().__init__(status_code=status_code, detail=detail) - logger.error(f"API Error: {status_code} - {detail}") - -def handle_minio_error(e: Exception) -> Dict[str, str]: - """处理MinIO相关错误""" - logger.error(f"MinIO operation failed: {str(e)}") - return { - "status": "error", - "data": f"MinIO operation failed: {str(e)}" - } - -def handle_http_error(e: Exception) -> Dict[str, str]: - """处理HTTP请求错误""" - logger.error(f"HTTP request failed: {str(e)}") - return { - "status": "error", - "data": f"HTTP request failed: {str(e)}" - } - -def handle_validation_error(e: Exception) -> Dict[str, str]: - """处理数据验证错误""" - logger.error(f"Validation failed: {str(e)}") - return { - "status": "error", - "data": f"Validation failed: {str(e)}" - } - -def handle_general_error(e: Exception) -> Dict[str, str]: - """处理通用错误""" - logger.error(f"Unexpected error: {str(e)}") - return { - "status": "error", - "data": f"Unexpected error: {str(e)}" - } diff --git a/mars_toolkit/core/utils.py b/mars_toolkit/core/utils.py deleted file mode 100644 index 1a96f9b..0000000 --- a/mars_toolkit/core/utils.py +++ /dev/null @@ -1,75 +0,0 @@ -import os -import boto3 -import logging -import logging.config -from typing import Optional -from pydantic import Field -from pydantic_settings import BaseSettings - -logger = logging.getLogger(__name__) - -class Settings(BaseSettings): - # Material Project - mp_api_key: Optional[str] = Field(None, env="MP_API_KEY") - mp_endpoint: Optional[str] = Field(None, env="MP_ENDPOINT") - mp_topk: Optional[int] = Field(3, env="MP_TOPK") - - # Proxy - http_proxy: Optional[str] = Field(None, env="HTTP_PROXY") - https_proxy: Optional[str] = Field(None, env="HTTPS_PROXY") - - # FairChem - fairchem_model_path: Optional[str] = Field(None, env="FAIRCHEM_MODEL_PATH") - fmax: Optional[float] = Field(0.05, env="FMAX") - - # MinIO - minio_endpoint: Optional[str] = Field(None, env="MINIO_ENDPOINT") - internal_minio_endpoint: Optional[str] = Field(None, env="INTERNAL_MINIO_ENDPOINT") - minio_access_key: Optional[str] = Field(None, env="MINIO_ACCESS_KEY") - minio_secret_key: Optional[str] = Field(None, env="MINIO_SECRET_KEY") - minio_bucket: Optional[str] = Field("mars-toolkit", env="MINIO_BUCKET") - - class Config: - env_file = ".env" - env_file_encoding = "utf-8" - -def setup_logging(): - """配置日志记录""" - parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')) - log_file_path = os.path.join(parent_dir, 'mars_toolkit.log') - - logging.config.dictConfig({ - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'standard': { - 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', - 'datefmt': '%Y-%m-%d %H:%M:%S' - }, - }, - 'handlers': { - 'console': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'standard' - }, - 'file': { - 'level': 'DEBUG', - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': log_file_path, - 'maxBytes': 10485760, # 10MB - 'backupCount': 5, - 'formatter': 'standard' - } - }, - 'loggers': { - '': { - 'handlers': ['console', 'file'], - 'level': 'INFO', - 'propagate': True - } - } - }) - -# 初始化配置 -settings = Settings() diff --git a/mars_toolkit/query/__pycache__/dify_search.cpython-310.pyc b/mars_toolkit/query/__pycache__/dify_search.cpython-310.pyc index db793d710e3d228445a08ae475486b3fecddac90..f398bea13b0296b390cccd4db524211b9bf7d4be 100644 GIT binary patch delta 178 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 diff --git a/mars_toolkit/query/__pycache__/oqmd_query.cpython-310.pyc b/mars_toolkit/query/__pycache__/oqmd_query.cpython-310.pyc index 2f44dafacfa753eb9330c00805e58d925d8d1f42..8473082bc4aec6888a56f278e0a4a86b520407d9 100644 GIT binary patch delta 204 zcmXAjD-Hrd5JabYfAiT7C?G)~Fq{Y1lJqQQCR~670gfOD7l4EtpfDud0LdY6cwpGA z67?!&>f68d%0*FV5%hk(=8tk;4p+<2STHz2Z302lk)n|3VxLd~|1EN<&nRm+cG4?S z4JS@}Lk5Hu$J`btpwb#nIb*p)R#w@Hb&UhT##VLAIj5U=4b)+nMq*N2W^h-KAI4@2 a-NV(KN5gPR3JDVO)~JIKG~gGp^Z5Z5W+?Ii delta 397 zcmX9)Jxjw-6wP~0+w?a5(7_K7c{tg@4vv{D?NTt5(k{NGJo8@LK=M+P7ojc{bP&Wr zI=LwbE)Mz!1pkJ=!C&BDP2xSnIS1}J7w*UAYO5MlDxf9v@bW#msoht17MF?R8r3-B z*h3U$j494INA-krUfwUz0!nvuujm`p$aLN-`9LAl1+VOz)I^#7C;l*qH3em+H#{;cVy?BFO-|mopEW$al10H(XlJ?6VS~Bw4kB4=f^L%9a7zR?2F?kzq9Qy#h#E;p+mgf_bcAn} zOeU3q0)NKF&@Agt9(V}K-zz)x;ZyIAvvGICm~?`%?(APTgKa449#|^NhNB*tV9^O< zugD)prapb&(#q9}w$H5%Exo#2P_y08#CuVTrr+$xMfU=fv1FXFqS)mv3;%^~H K#A9?k!J{AIbCB%- literal 0 HcmV?d00001 diff --git a/mars_toolkit/services/__pycache__/mattergen_service.cpython-310.pyc b/mars_toolkit/services/__pycache__/mattergen_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77575bdcfe98941f0eff44907ee46bcb9dd6b7fe GIT binary patch literal 9776 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 literal 0 HcmV?d00001 diff --git a/mars_toolkit/services/mattergen_service.py b/mars_toolkit/services/mattergen_service.py new file mode 100644 index 0000000..1578268 --- /dev/null +++ b/mars_toolkit/services/mattergen_service.py @@ -0,0 +1,342 @@ +""" +MatterGen service for mars_toolkit. + +This module provides a service for generating crystal structures using MatterGen. +The service initializes the CrystalGenerator once and reuses it for multiple +generation requests, improving performance. +""" + +import os +import logging +import json +from pathlib import Path +from typing import Dict, Any, Optional, Union, List +import threading + +# 导入mattergen相关模块 +import sys +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../'))) +from mattergen_wrapper import generator +CrystalGenerator = generator.CrystalGenerator +from mattergen.common.data.types import TargetProperty +from mattergen.common.utils.eval_utils import MatterGenCheckpointInfo +from mattergen.common.utils.data_classes import PRETRAINED_MODEL_NAME + +# 导入mars_toolkit配置 +from mars_toolkit.core.config import config + +logger = logging.getLogger(__name__) + +class MatterGenService: + """ + Service for generating crystal structures using MatterGen. + + This service initializes the CrystalGenerator once and reuses it for multiple + generation requests, improving performance. + """ + + _instance = None + _lock = threading.Lock() + + @classmethod + def get_instance(cls): + """ + Get the singleton instance of MatterGenService. + + Returns: + MatterGenService: The singleton instance. + """ + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def __init__(self): + """ + Initialize the MatterGenService. + + This initializes the base generator without any property conditioning. + Specific generators for different property conditions will be initialized + on demand. + """ + self._generators = {} + self._output_dir = config.MATTERGENMODEL_RESULT_PATH + + # 确保输出目录存在 + if not os.path.exists(self._output_dir): + os.makedirs(self._output_dir) + + # 初始化基础生成器(无条件生成) + self._init_base_generator() + + def _init_base_generator(self): + """ + Initialize the base generator for unconditional generation. + """ + model_path = os.path.join(config.MATTERGENMODEL_ROOT, "mattergen_base") + + if not os.path.exists(model_path): + logger.warning(f"Base model directory not found at {model_path}. MatterGen service may not work properly.") + return + + logger.info(f"Initializing base MatterGen generator from {model_path}") + + try: + checkpoint_info = MatterGenCheckpointInfo( + model_path=Path(model_path).resolve(), + load_epoch="last", + config_overrides=[], + strict_checkpoint_loading=True, + ) + + generator = CrystalGenerator( + checkpoint_info=checkpoint_info, + properties_to_condition_on=None, + batch_size=2, # 默认值,可在生成时覆盖 + num_batches=1, # 默认值,可在生成时覆盖 + sampling_config_name="default", + sampling_config_path=None, + sampling_config_overrides=[], + record_trajectories=True, + diffusion_guidance_factor=0.0, + target_compositions_dict=[], + ) + + self._generators["base"] = generator + logger.info("Base MatterGen generator initialized successfully") + except Exception as e: + logger.error(f"Failed to initialize base MatterGen generator: {e}") + + def _get_or_create_generator( + self, + properties: Optional[Dict[str, Any]] = None, + batch_size: int = 2, + num_batches: int = 1, + diffusion_guidance_factor: float = 2.0 + ): + """ + Get or create a generator for the specified properties. + + Args: + properties: Optional property constraints + batch_size: Number of structures per batch + num_batches: Number of batches to generate + diffusion_guidance_factor: Controls adherence to target properties + + Returns: + tuple: (generator, generator_key, properties_to_condition_on) + """ + # 如果没有属性约束,使用基础生成器 + if not properties: + if "base" not in self._generators: + self._init_base_generator() + return self._generators.get("base"), "base", None + + # 处理属性约束 + properties_to_condition_on = {} + for property_name, property_value in properties.items(): + properties_to_condition_on[property_name] = property_value + + # 确定模型目录 + if len(properties) == 1: + # 单属性条件 + property_name = list(properties.keys())[0] + property_to_model = { + "dft_mag_density": "dft_mag_density", + "dft_bulk_modulus": "dft_bulk_modulus", + "dft_shear_modulus": "dft_shear_modulus", + "energy_above_hull": "energy_above_hull", + "formation_energy_per_atom": "formation_energy_per_atom", + "space_group": "space_group", + "hhi_score": "hhi_score", + "ml_bulk_modulus": "ml_bulk_modulus", + "chemical_system": "chemical_system", + "dft_band_gap": "dft_band_gap" + } + model_dir = property_to_model.get(property_name, property_name) + generator_key = f"single_{property_name}" + else: + # 多属性条件 + property_keys = set(properties.keys()) + if property_keys == {"dft_mag_density", "hhi_score"}: + model_dir = "dft_mag_density_hhi_score" + generator_key = "multi_dft_mag_density_hhi_score" + elif property_keys == {"chemical_system", "energy_above_hull"}: + model_dir = "chemical_system_energy_above_hull" + generator_key = "multi_chemical_system_energy_above_hull" + else: + # 如果没有特定的多属性模型,使用第一个属性的模型 + first_property = list(properties.keys())[0] + model_dir = first_property + generator_key = f"multi_{first_property}_etc" + + # 构建完整的模型路径 + model_path = os.path.join(config.MATTERGENMODEL_ROOT, model_dir) + + # 检查模型目录是否存在 + if not os.path.exists(model_path): + # 如果特定模型不存在,回退到基础模型 + logger.warning(f"Model directory for {model_dir} not found. Using base model instead.") + model_path = os.path.join(config.MATTERGENMODEL_ROOT, "mattergen_base") + generator_key = "base" + + # 检查是否已经有这个生成器 + if generator_key in self._generators: + # 更新生成器的参数 + generator = self._generators[generator_key] + generator.batch_size = batch_size + generator.num_batches = num_batches + generator.diffusion_guidance_factor = diffusion_guidance_factor if properties else 0.0 + return generator, generator_key, properties_to_condition_on + + # 创建新的生成器 + try: + logger.info(f"Initializing new MatterGen generator for {generator_key} from {model_path}") + + checkpoint_info = MatterGenCheckpointInfo( + model_path=Path(model_path).resolve(), + load_epoch="last", + config_overrides=[], + strict_checkpoint_loading=True, + ) + + generator = CrystalGenerator( + checkpoint_info=checkpoint_info, + properties_to_condition_on=properties_to_condition_on, + batch_size=batch_size, + num_batches=num_batches, + sampling_config_name="default", + sampling_config_path=None, + sampling_config_overrides=[], + record_trajectories=True, + diffusion_guidance_factor=diffusion_guidance_factor if properties else 0.0, + target_compositions_dict=[], + ) + + self._generators[generator_key] = generator + logger.info(f"MatterGen generator for {generator_key} initialized successfully") + return generator, generator_key, properties_to_condition_on + except Exception as e: + logger.error(f"Failed to initialize MatterGen generator for {generator_key}: {e}") + # 回退到基础生成器 + if "base" not in self._generators: + self._init_base_generator() + return self._generators.get("base"), "base", None + + def generate( + self, + properties: Optional[Dict[str, Union[float, str, Dict[str, Union[float, str]]]]] = None, + batch_size: int = 2, + num_batches: int = 1, + diffusion_guidance_factor: float = 2.0 + ) -> str: + """ + Generate crystal structures with optional property constraints. + + Args: + properties: Optional property constraints + batch_size: Number of structures per batch + num_batches: Number of batches to generate + diffusion_guidance_factor: Controls adherence to target properties + + Returns: + str: Descriptive text with generated crystal structures in CIF format + """ + from mars_toolkit.compute.material_gen import format_cif_content + + # 处理字符串输入(如果提供) + if isinstance(properties, str): + try: + properties = json.loads(properties) + except json.JSONDecodeError: + raise ValueError(f"Invalid properties JSON string: {properties}") + + # 如果为None,默认为空字典 + properties = properties or {} + + # 获取或创建生成器 + generator, generator_key, properties_to_condition_on = self._get_or_create_generator( + properties, batch_size, num_batches, diffusion_guidance_factor + ) + + if generator is None: + return "Error: Failed to initialize MatterGen generator" + + # 生成结构 + try: + generator.generate(output_dir=Path(self._output_dir)) + except Exception as e: + logger.error(f"Error generating structures: {e}") + return f"Error generating structures: {e}" + + # 创建字典存储文件内容 + result_dict = {} + + # 定义文件路径 + cif_zip_path = os.path.join(self._output_dir, "generated_crystals_cif.zip") + xyz_file_path = os.path.join(self._output_dir, "generated_crystals.extxyz") + trajectories_zip_path = os.path.join(self._output_dir, "generated_trajectories.zip") + + # 读取CIF压缩文件 + if os.path.exists(cif_zip_path): + with open(cif_zip_path, 'rb') as f: + result_dict['cif_content'] = f.read() + + # 根据生成类型创建描述性提示 + if not properties: + generation_type = "unconditional" + title = "Generated Material Structures" + description = "These structures were generated unconditionally, meaning no specific properties were targeted." + property_description = "unconditionally" + elif len(properties) == 1: + generation_type = "single_property" + property_name = list(properties.keys())[0] + property_value = properties[property_name] + title = f"Generated Material Structures Conditioned on {property_name} = {property_value}" + description = f"These structures were generated with property conditioning, targeting a {property_name} value of {property_value}." + property_description = f"conditioned on {property_name} = {property_value}" + else: + generation_type = "multi_property" + title = "Generated Material Structures Conditioned on Multiple Properties" + description = "These structures were generated with multi-property conditioning, targeting the specified property values." + property_description = f"conditioned on multiple properties: {', '.join([f'{name} = {value}' for name, value in properties.items()])}" + + # 创建完整的提示 + prompt = f""" +# {title} + +This data contains {batch_size * num_batches} crystal structures generated by the MatterGen model, {property_description}. + + {'' if generation_type == 'unconditional' else f''' + A diffusion guidance factor of {diffusion_guidance_factor} was used, which controls how strongly + the generation adheres to the specified property values. Higher values produce samples that more + closely match the target properties but may reduce diversity. + '''} + +## CIF Files (Crystallographic Information Files) + +- Standard format for crystallographic structures +- Contains unit cell parameters, atomic positions, and symmetry information +- Used by crystallographic software and visualization tools + +``` +{format_cif_content(result_dict.get('cif_content', b'').decode('utf-8', errors='replace') if isinstance(result_dict.get('cif_content', b''), bytes) else str(result_dict.get('cif_content', '')))} +``` + +{description} +You can use these structures for materials discovery, property prediction, or further analysis. +""" + + # 清理文件(读取后删除) + try: + if os.path.exists(cif_zip_path): + os.remove(cif_zip_path) + if os.path.exists(xyz_file_path): + os.remove(xyz_file_path) + if os.path.exists(trajectories_zip_path): + os.remove(trajectories_zip_path) + except Exception as e: + logger.warning(f"Error cleaning up files: {e}") + + return prompt diff --git a/mars_toolkit/visualization/__init__.py b/mars_toolkit/visualization/__init__.py index e69de29..e714c1f 100644 --- a/mars_toolkit/visualization/__init__.py +++ b/mars_toolkit/visualization/__init__.py @@ -0,0 +1,2 @@ + +f diff --git a/mars_toolkit/visualization/__pycache__/__init__.cpython-310.pyc b/mars_toolkit/visualization/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce3c41370fa75945199d1019cb8331034873536d GIT binary patch literal 578 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