Files
2025-05-09 14:16:33 +08:00

213 lines
7.7 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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.
"""
import json
import logging
import os
from ase.io import read
import tempfile
from typing import Optional, Tuple
from ase import Atoms
from ...core.config import material_config
logger = logging.getLogger(__name__)
def read_cif_txt_file(file_path):
"""
Read the CIF file and return its content.
Args:
file_path: Path to the CIF file
Returns:
String content of the CIF file or None if an error occurs
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
logger.error(f"Error reading file {file_path}: {e}")
return None
def extract_cif_info(path: str, fields_name: list):
"""
Extract specific fields from the CIF description JSON file.
Args:
path: Path to the JSON file containing CIF information
fields_name: List of field categories to extract. Use 'all_fields' to extract all fields.
Other options include 'basic_fields', 'energy_electronic_fields', 'metal_magentic_fields'
Returns:
Dictionary containing the extracted fields
"""
basic_fields = ['formula_pretty', 'chemsys', 'composition', 'elements', 'symmetry', 'nsites', 'volume', 'density']
energy_electronic_fields = ['formation_energy_per_atom', 'energy_above_hull', 'is_stable', 'efermi', 'cbm', 'vbm', 'band_gap', 'is_gap_direct']
metal_magentic_fields = ['is_metal', 'is_magnetic', "ordering", 'total_magnetization', 'num_magnetic_sites']
selected_fields = []
if fields_name[0] == 'all_fields':
selected_fields = basic_fields + energy_electronic_fields + metal_magentic_fields
else:
for field in fields_name:
selected_fields.extend(locals().get(field, []))
with open(path, 'r') as f:
docs = json.load(f)
new_docs = {}
for field_name in selected_fields:
new_docs[field_name] = docs.get(field_name, '')
return new_docs
def remove_symmetry_equiv_xyz(cif_content):
"""
Remove symmetry operations section from CIF file content.
This is often useful when working with CIF files in certain visualization tools
or when focusing on the basic structure without symmetry operations.
Args:
cif_content: CIF file content string
Returns:
Cleaned CIF content string with symmetry operations removed
"""
lines = cif_content.split('\n')
output_lines = []
i = 0
while i < len(lines):
line = lines[i].strip()
# 检测循环开始
if line == 'loop_':
# 查看下一行,检查是否是对称性循环
next_lines = []
j = i + 1
while j < len(lines) and lines[j].strip().startswith('_'):
next_lines.append(lines[j].strip())
j += 1
# 检查是否包含对称性操作标签
if any('_symmetry_equiv_pos_as_xyz' in tag for tag in next_lines):
# 跳过整个循环块
while i < len(lines):
if i + 1 >= len(lines):
break
next_line = lines[i + 1].strip()
# 检查是否到达下一个循环或数据块
if next_line == 'loop_' or next_line.startswith('data_'):
break
# 检查是否到达原子位置部分
if next_line.startswith('_atom_site_'):
break
i += 1
else:
# 不是对称性循环保留loop_行
output_lines.append(lines[i])
else:
# 非循环开始行,直接保留
output_lines.append(lines[i])
i += 1
return '\n'.join(output_lines)
def read_structure_from_file_name_or_content_string(file_name_or_content_string: str, format_type: str = "auto") -> Tuple[str, str]:
"""
处理结构输入,判断是文件名还是直接内容
当file_name_or_content_string被视为文件名时会在material_config.TEMP_ROOT目录下查找该文件。
这适用于大模型生成的临时文件,这些文件通常存储在临时目录中。
Args:
file_name_or_content_string: 文件名或结构内容字符串
format_type: 结构格式类型,"auto"表示自动检测
Returns:
tuple: (内容字符串, 实际格式类型)
"""
# 首先检查是否是完整路径的文件
if os.path.exists(file_name_or_content_string) and os.path.isfile(file_name_or_content_string):
# 是完整路径文件,读取文件内容
with open(file_name_or_content_string, 'r', encoding='utf-8') as f:
content = f.read()
# 如果格式为auto从文件扩展名推断
if format_type == "auto":
ext = os.path.splitext(file_name_or_content_string)[1].lower().lstrip('.')
if ext in ['cif', 'xyz', 'vasp', 'poscar']:
format_type = 'cif' if ext == 'cif' else 'xyz' if ext == 'xyz' else 'vasp'
else:
# 默认假设为CIF
format_type = 'cif'
else:
# 检查是否是临时目录中的文件名
temp_path = os.path.join(material_config.TEMP_ROOT, file_name_or_content_string)
if os.path.exists(temp_path) and os.path.isfile(temp_path):
# 是临时目录中的文件,读取文件内容
with open(temp_path, 'r', encoding='utf-8') as f:
content = f.read()
# 如果格式为auto从文件扩展名推断
if format_type == "auto":
ext = os.path.splitext(temp_path)[1].lower().lstrip('.')
if ext in ['cif', 'xyz', 'vasp', 'poscar']:
format_type = 'cif' if ext == 'cif' else 'xyz' if ext == 'xyz' else 'vasp'
else:
# 默认假设为CIF
format_type = 'cif'
else:
# 不是文件路径,假设是直接内容
content = file_name_or_content_string
# 如果格式为auto尝试从内容推断
if format_type == "auto":
# 简单启发式判断:
# CIF文件通常包含"data_"和"_cell_"
if "data_" in content and "_cell_" in content:
format_type = "cif"
# XYZ文件通常第一行是原子数量
elif content.strip().split('\n')[0].strip().isdigit():
format_type = "xyz"
# POSCAR/VASP格式通常第一行是注释
elif len(content.strip().split('\n')) > 5 and all(len(line.split()) == 3 for line in content.strip().split('\n')[2:5]):
format_type = "vasp"
# 默认假设为CIF
else:
format_type = "cif"
return content, format_type
def convert_structure(input_format: str='cif', content: str=None) -> Optional[Atoms]:
"""
将输入内容转换为Atoms对象
Args:
input_format: 输入格式 (cif, xyz, vasp等)
content: 结构内容字符串
Returns:
ASE Atoms对象如果转换失败则返回None
"""
try:
with tempfile.NamedTemporaryFile(suffix=f".{input_format}", mode="w", delete=False) as tmp_file:
tmp_file.write(content)
tmp_path = tmp_file.name
atoms = read(tmp_path)
os.unlink(tmp_path)
return atoms
except Exception as e:
logger.error(f"Failed to convert structure: {str(e)}")
return None