CSM计算

This commit is contained in:
2025-09-22 11:18:39 +08:00
parent 71f6ae8928
commit 28c2323ce8
4 changed files with 307 additions and 34 deletions

View File

@@ -0,0 +1,134 @@
from pymatgen.core.structure import Structure
from pymatgen.analysis.local_env import VoronoiNN
import numpy as np
def check_real(nearest):
real_nearest = []
for site in nearest:
if np.all((site.frac_coords >= 0) & (site.frac_coords <= 1)):
real_nearest.append(site)
return real_nearest
def special_check_for_3(site, nearest):
real_nearest = []
distances = []
for site2 in nearest:
distance = np.linalg.norm(np.array(site.frac_coords) - np.array(site2.frac_coords))
distances.append(distance)
sorted_indices = np.argsort(distances)
for index in sorted_indices[:3]:
real_nearest.append(nearest[index])
return real_nearest
def CS_catulate(struct, sp='Li', anion=['O'], tol=0, cutoff=3.0,notice=False,ID=None):
"""
计算结构中目标元素与最近阴离子的共享关系。
参数:
struct (Structure): 输入结构。
sp (str): 目标元素符号,默认为 'Li'
anion (list): 阴离子列表,默认为 ['O']。
tol (float): VoronoiNN 的容差,默认为 0。
cutoff (float): VoronoiNN 的截断距离,默认为 3.0。
返回:
list: 包含每个目标位点及其最近阴离子索引的列表。
"""
# 初始化 VoronoiNN 对象
if sp=='Li':
tol = 0
cutoff = 3.0
voro_nn = VoronoiNN(tol=tol, cutoff=cutoff)
# 初始化字典,用于统计共享关系
shared_count = {"2": 0, "3": 0,"4":0,"5":0,"6":0}
# 存储结果的列表
atom_dice = []
# 遍历结构中的每个位点
for index,site in enumerate(struct.sites):
# 跳过阴离子位点
if site.specie.symbol in anion:
continue
# 跳过Li原子
if site.specie.symbol == sp:
continue
# 获取 Voronoi 多面体信息
voro_info = voro_nn.get_voronoi_polyhedra(struct, index)
# 找到最近的阴离子位点
nearest_anions = [
nn_info["site"] for nn_info in voro_info.values()
if nn_info["site"].specie.symbol in anion
]
# 如果没有找到最近的阴离子,跳过
if not nearest_anions:
print(f"No nearest anions found for {ID} site {index}.")
continue
if site.specie.symbol == 'B' or site.specie.symbol == 'N':
nearest_anions = special_check_for_3(site,nearest_anions)
nearest_anions = check_real(nearest_anions)
# 将结果添加到 atom_dice 列表中
atom_dice.append({
'index': index,
'nearest_index': [nn.index for nn in nearest_anions]
})
# 枚举 atom_dice 中的所有原子对
for i, atom_i in enumerate(atom_dice):
for j, atom_j in enumerate(atom_dice[i + 1:], start=i + 1):
# 获取两个原子的最近阴离子索引
nearest_i = set(atom_i['nearest_index'])
nearest_j = set(atom_j['nearest_index'])
# 比较最近阴离子的交集大小
shared_count_key = str(len(nearest_i & nearest_j))
# 更新字典中的计数
if shared_count_key in shared_count:
shared_count[shared_count_key] += 1
if notice:
if shared_count_key=='2':
print(f"{atom_j['index']}{atom_i['index']}之间存在共线")
print(f"共线的阴离子为{nearest_i & nearest_j}")
if shared_count_key=='3':
print(f"{atom_j['index']}{atom_i['index']}之间存在共面")
print(f"共面的阴离子为{nearest_i & nearest_j}")
# # 最后将字典中的值除以 2因为每个共享关系被计算了两次
# for key in shared_count.keys():
# shared_count[key] //= 2
return shared_count
def CS_count(struct, shared_count, sp='Li'):
count = 0
for site in struct.sites:
if site.specie.symbol == sp:
count += 1 # 累加符合条件的原子数量
CS_count = 0
for i in range(2, 7): # 遍历范围 [2, 3, 4, 5]
if str(i) in shared_count: # 检查键是否存在
CS_count += shared_count[str(i)] * i # 累加计算结果
if count > 0: # 防止除以零
CS_count /= count # 平均化结果
else:
CS_count = 0 # 如果 count 为 0直接返回 0
return CS_count
structure = Structure.from_file("data/CS_Table1/Li3Al(BO3)2_mp-6097_computed.cif")
a = CS_catulate(structure,notice=True)
b = CS_count(structure,a)
print(f"{a}\n{b}")

View File

@@ -47,28 +47,19 @@ class HiddenPrints:
def non_elements(struct, sp='Li'):
'''
struct : structure object from Pymatgen
"""
struct : 必须是一个有序结构
sp : the mobile specie
returns the structure with all of the mobile specie (Li) removed
'''
num_li = struct.species.count(Element(sp))
species = list(set(struct.species))
try:
species.remove(Element("O"))
except ValueError:
print("没有O")
try:
species.remove(Element("S"))
except ValueError:
print("没有S")
try:
species.remove(Element("N"))
except ValueError:
print("没有N")
returns a new structure containing only the framework anions (O, S, N).
"""
anions_to_keep = {"O", "S", "N"}
stripped = struct.copy()
stripped.remove_species(species)
stripped = stripped.get_sorted_structure(reverse=True)
species_to_remove = [el.symbol for el in stripped.composition.elements
if el.symbol not in anions_to_keep]
if species_to_remove:
stripped.remove_species(species_to_remove)
return stripped
@@ -160,22 +151,43 @@ def site_env(coord, struct, sp="Li", envtype='both'):
def extract_sites(struct, sp="Li", envtype='both'):
'''
"""
struct : structure object from Pymatgen
envtype : 'tet', 'oct', or 'both'
sp : target element to analyze environment
'''
"""
envlist = []
for i in range(len(struct.sites)):
if struct.sites[i].specie != Element(sp):
continue
site = struct.sites[i]
singleenv = site_env(site.frac_coords, struct, sp, envtype)
envlist.append({'frac_coords': site.frac_coords, 'type': singleenv['type'], 'csm': singleenv['csm'],
'volume': singleenv['vol']})
return envlist
# --- 关键修改:直接遍历原始结构,即使它是无序的 ---
# 我们不再调用 get_sorted_structure()
# 我们只关心那些含有目标元素 sp 的位点
# 遍历每一个位点 (site)
for i, site in enumerate(struct):
# 检查当前位点的组分(site.species)中是否包含我们感兴趣的元素(sp)
# site.species.elements 返回该位点上的元素列表,例如 [Element Li, Element Fe]
# [el.symbol for el in site.species.elements] 将其转换为符号列表 ['Li', 'Fe']
site_elements = [el.symbol for el in site.species.elements]
if sp in site_elements:
# 如果找到了Li我们就对这个位点进行环境分析
# 注意:我们将原始的、可能无序的 struct 传递给 site_env
# 因为 site_env 内部的函数 (如 LocalGeometryFinder) 知道如何处理它
# 为了让下游函数(特别是 non_elements能够工作
# 我们在这里创建一个一次性的、临时的有序结构副本给它
# 这可以避免我们之前遇到的所有 'ordered structures only' 错误
temp_ordered_struct = struct.get_sorted_structure()
singleenv = site_env(site.frac_coords, temp_ordered_struct, sp, envtype)
envlist.append({'frac_coords': site.frac_coords, 'type': singleenv['type'], 'csm': singleenv['csm'],
'volume': singleenv['vol']})
if not envlist:
print(f"警告: 在结构中未找到元素 {sp} 的占位。")
return envlist
def export_envs(envlist, sp='Li', envtype='both', fname=None):
'''
@@ -193,6 +205,6 @@ def export_envs(envlist, sp='Li', envtype='both', fname=None):
f.write("Site index " + str(index) + ": " + str(i) + '\n')
struct = Structure.from_file("../data/31960.cif")
site_info = extract_sites(struct, envtype="both")
export_envs(site_info, sp="Li", envtype="both")
# struct = Structure.from_file("../data/0921/wjy_475.cif")
# site_info = extract_sites(struct, envtype="both")
# export_envs(site_info, sp="Li", envtype="both")

127
data_get/data_get.py Normal file
View File

@@ -0,0 +1,127 @@
import pandas as pd
import os
import re
def extract_cif_from_xlsx(
xlsx_path: str,
output_dir: str,
naming_mode: str = 'formula',
name_col: int = 0,
cif_col: int = 1,
prefix: str = 'wjy'
):
"""
从 XLSX 文件中提取 CIF 数据并保存为单独的 .cif 文件。
Args:
xlsx_path (str): 输入的 XLSX 文件的路径。
output_dir (str): 输出 .cif 文件的文件夹路径。
naming_mode (str, optional): CIF 文件的命名模式。
可选值为 'formula' (使用第一列的名字) 或
'auto' (使用前缀+自动递增编号)。
默认为 'formula'
name_col (int, optional): 包含文件名的列的索引从0开始。默认为 0。
cif_col (int, optional): 包含 CIF 内容的列的索引从0开始。默认为 1。
prefix (str, optional): 在 'auto' 命名模式下使用的文件名前缀。默认为 'wjy'
Raises:
FileNotFoundError: 如果指定的 xlsx_path 文件不存在。
ValueError: 如果指定的 naming_mode 无效。
Exception: 处理过程中发生的其他错误。
"""
# --- 1. 参数校验和准备 ---
if not os.path.exists(xlsx_path):
raise FileNotFoundError(f"错误: 输入文件未找到 -> {xlsx_path}")
if naming_mode not in ['formula', 'auto']:
raise ValueError(f"错误: 'naming_mode' 参数必须是 'formula''auto',但收到了 '{naming_mode}'")
# 创建输出目录(如果不存在)
os.makedirs(output_dir, exist_ok=True)
print(f"CIF 文件将保存到: {output_dir}")
try:
# --- 2. 读取 XLSX 文件 ---
# header=None 表示第一行不是标题,将其作为数据读取
df = pd.read_excel(xlsx_path, header=None)
# 跳过原始文件的表头行('formula', 'cif'
if str(df.iloc[0, name_col]).strip().lower() == 'formula' and str(df.iloc[0, cif_col]).strip().lower() == 'cif':
df = df.iloc[1:]
print("检测到并跳过了表头行。")
# --- 3. 遍历数据并生成文件 ---
success_count = 0
for index, row in df.iterrows():
# 获取文件名和 CIF 内容
formula_name = str(row[name_col])
cif_content = str(row[cif_col])
# 跳过内容为空的行
if pd.isna(row[name_col]) or pd.isna(row[cif_col]) or not cif_content.strip():
print(f"警告: 第 {index + 2} 行数据不完整,已跳过。")
continue
# --- 4. 根据命名模式确定文件名 ---
if naming_mode == 'formula':
# 清理文件名,替换掉不适合做文件名的特殊字符
# 例如:将 (PO4)3 替换为 _PO4_3将 / 替换为 _
safe_filename = re.sub(r'[\\/*?:"<>|()]', '_', formula_name)
filename = f"{safe_filename}.cif"
else: # naming_mode == 'auto'
# 使用 format 方法来确保编号格式统一,例如 001, 002
filename = f"{prefix}_{success_count + 1:03d}.cif"
# 构造完整的输出文件路径
output_path = os.path.join(output_dir, filename)
# --- 5. 写入 CIF 文件 ---
try:
with open(output_path, 'w', encoding='utf-8') as f:
f.write(cif_content)
success_count += 1
except IOError as e:
print(f"错误: 无法写入文件 {output_path}。原因: {e}")
print(f"\n处理完成!成功提取并生成了 {success_count} 个 CIF 文件。")
except Exception as e:
print(f"处理 XLSX 文件时发生错误: {e}")
# --- 函数使用示例 ---
if __name__ == '__main__':
# 假设您的 XLSX 文件名为 'materials.xlsx',且与此脚本在同一目录下
source_xlsx_file = 'input/cif_dataset.xlsx'
# 检查示例文件是否存在,如果不存在则创建一个
if not os.path.exists(source_xlsx_file):
print(f"未找到示例文件 '{source_xlsx_file}',正在创建一个...")
example_data = {
'formula': ['Li3Al0.3Ti1.7(PO4)3', 'Li6.5La3Zr1.75W0.25O12', 'Invalid/Name*Test'],
'cif': ['# CIF Data for Li3Al0.3...\n_atom_site_type_symbol\n Li\n Al\n Ti\n P\n O',
'# CIF Data for Li6.5La3...\n_symmetry_space_group_name_H-M \'I a -3 d\'',
'# CIF Data for Invalid Name Test']
}
pd.DataFrame(example_data).to_excel(source_xlsx_file, index=False, header=True)
print("示例文件创建成功。")
# --- 示例 1: 使用第一列的 'formula' 命名 ---
# print("\n--- 示例 1: 使用 'formula' 命名模式 ---")
# output_folder_1 = 'cif_by_formula'
# extract_cif_from_xlsx(
# xlsx_path=source_xlsx_file,
# output_dir=output_folder_1,
# naming_mode='formula'
# )
# --- 示例 2: 使用 'wjy+编号' 自动命名 ---
print("\n--- 示例 2: 使用 'auto' 命名模式 ---")
output_folder_2 = 'cif_by_auto'
extract_cif_from_xlsx(
xlsx_path=source_xlsx_file,
output_dir=output_folder_2,
naming_mode='auto',
prefix='wjy'
)

Binary file not shown.