Files
solidstate-tools/corner-sharing/utils/CS_analyse.py
2025-09-23 19:39:54 +08:00

356 lines
14 KiB
Python
Raw 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.

from typing import List, Dict
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: str = 'Li',
anion: List[str] = ['O'],
tol: float = 0,
cutoff: float = 3.0,
notice: bool = False
) -> Dict[str, Dict[str, int]]:
"""
计算结构中不同类型阳离子多面体之间的共享关系(角、边、面共享)。
该函数会分别计算以下三种情况的共享数量:
1. 目标原子 vs 目标原子 (e.g., Li-Li)
2. 目标原子 vs 其他阳离子 (e.g., Li-X)
3. 其他阳离子 vs 其他阳离子 (e.g., X-Y)
参数:
struct (Structure): 输入的pymatgen结构对象。
sp (str): 目标元素符号,默认为 'Li'
anion (list): 阴离子元素符号列表,默认为 ['O']。
tol (float): VoronoiNN 的容差。对于Li通常设为0。
cutoff (float): VoronoiNN 的截断距离。对于Li通常设为3.0。
notice (bool): 是否打印详细的共享信息。
返回:
dict: 一个字典,包含三类共享关系的统计结果。
"sp_vs_sp", "sp_vs_other", "other_vs_other" 分别对应上述三种情况。
每个键的值是另一个字典统计了共享2个(边)、3个(面)等情况的数量。
例如: {'sp_vs_sp': {'1': 10, '2': 4}, 'sp_vs_other': ...}
共享1个阴离子为角共享2个为边共享3个为面共享。
"""
# 初始化 VoronoiNN 对象
voro_nn = VoronoiNN(tol=tol, cutoff=cutoff)
# 1. 分类存储所有阳离子的近邻阴离子信息
target_sites_info = []
other_cation_sites_info = []
for index, site in enumerate(struct.sites):
# 跳过阴离子本身
if site.species.chemical_system in anion:
continue
# 获取当前位点的近邻阴离子
try:
# 使用 get_nn_info 更直接
nn_info = voro_nn.get_nn_info(struct, index)
nearest_anions = [
nn["site"] for nn in nn_info
if nn["site"].species.chemical_system in anion
]
except Exception as e:
print(f"Warning: Could not get neighbors for site {index} ({site.species_string}): {e}")
continue
if not nearest_anions:
continue
# 整理信息
site_info = {
'index': index,
'element': site.species.chemical_system,
'nearest_anion_indices': {nn.index for nn in nearest_anions}
}
# 根据是否为目标原子进行分类
if site.species.chemical_system == sp:
target_sites_info.append(site_info)
else:
other_cation_sites_info.append(site_info)
# 2. 初始化结果字典
# 共享数量key: 1-角, 2-边, 3-面
results = {
"sp_vs_sp": {"1": 0, "2": 0, "3": 0, "4": 0},
"sp_vs_other": {"1": 0, "2": 0, "3": 0, "4": 0},
"other_vs_other": {"1": 0, "2": 0, "3": 0, "4": 0},
}
# 3. 计算不同类别之间的共享关系
# 3.1 目标原子 vs 目标原子 (sp_vs_sp)
for i in range(len(target_sites_info)):
for j in range(i + 1, len(target_sites_info)):
atom_i = target_sites_info[i]
atom_j = target_sites_info[j]
shared_anions = atom_i['nearest_anion_indices'].intersection(atom_j['nearest_anion_indices'])
shared_count = len(shared_anions)
if shared_count > 0 and str(shared_count) in results["sp_vs_sp"]:
results["sp_vs_sp"][str(shared_count)] += 1
if notice:
print(
f"[Li-Li] Atom {atom_i['index']} and {atom_j['index']} share {shared_count} anions: {shared_anions}")
# 3.2 目标原子 vs 其他阳离子 (sp_vs_other)
for atom_sp in target_sites_info:
for atom_other in other_cation_sites_info:
shared_anions = atom_sp['nearest_anion_indices'].intersection(atom_other['nearest_anion_indices'])
shared_count = len(shared_anions)
if shared_count > 0 and str(shared_count) in results["sp_vs_other"]:
results["sp_vs_other"][str(shared_count)] += 1
if notice:
print(
f"[Li-Other] Atom {atom_sp['index']} and {atom_other['index']} share {shared_count} anions: {shared_anions}")
# 3.3 其他阳离子 vs 其他阳离子 (other_vs_other)
for i in range(len(other_cation_sites_info)):
for j in range(i + 1, len(other_cation_sites_info)):
atom_i = other_cation_sites_info[i]
atom_j = other_cation_sites_info[j]
shared_anions = atom_i['nearest_anion_indices'].intersection(atom_j['nearest_anion_indices'])
shared_count = len(shared_anions)
if shared_count > 0 and str(shared_count) in results["other_vs_other"]:
results["other_vs_other"][str(shared_count)] += 1
if notice:
print(
f"[Other-Other] Atom {atom_i['index']} and {atom_j['index']} share {shared_count} anions: {shared_anions}")
return results
def CS_catulate_old(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.species.chemical_system in anion:
continue
# 跳过Li原子
if site.species.chemical_system == 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"].species.chemical_system in anion
]
# 如果没有找到最近的阴离子,跳过
if not nearest_anions:
print(f"No nearest anions found for {ID} site {index}.")
continue
if site.species.chemical_system == 'B' or site.species.chemical_system == '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, sharing_results: Dict[str, Dict[str, int]], sp: str = 'Li') -> float:
"""
分析多面体共享结果,计算平均每个目标原子参与的共享阴离子数。
这个函数是 calculate_polyhedra_sharing 的配套函数。
参数:
struct (Structure): 输入的pymatgen结构对象用于统计目标原子总数。
sharing_results (dict): 来自 calculate_polyhedra_sharing 函数的输出结果。
sp (str): 目标元素符号,默认为 'Li'
返回:
float: 平均每个目标原子sp参与的共享阴离子数量。
例如结果为2.5意味着平均每个Li原子通过共享与其他阳离子
包括Li和其他阳离子连接了2.5个阴离子。
"""
# 1. 统计结构中目标原子的总数
target_atom_count = 0
for site in struct.sites:
if site.species.chemical_system == sp:
target_atom_count += 1
# 如果结构中没有目标原子直接返回0避免除以零错误
if target_atom_count == 0:
return 0.0
# 2. 计算加权的共享阴离子总数
total_shared_anions = 0
# 处理 sp_vs_sp (例如 Li-Li) 的共享
# 每个共享关系涉及两个目标原子,所以权重需要乘以 2
if "sp_vs_sp" in sharing_results:
sp_vs_sp_counts = sharing_results["sp_vs_sp"]
for num_shared_str, count in sp_vs_sp_counts.items():
num_shared = int(num_shared_str)
# 权重 = 共享阴离子数 * 涉及的目标原子数 (2) * 出现次数
total_shared_anions += num_shared * 2 * count
# 处理 sp_vs_other (例如 Li-X) 的共享
# 每个共享关系涉及一个目标原子,所以权重乘以 1
if "sp_vs_other" in sharing_results:
sp_vs_other_counts = sharing_results["sp_vs_other"]
for num_shared_str, count in sp_vs_other_counts.items():
num_shared = int(num_shared_str)
# 权重 = 共享阴离子数 * 涉及的目标原子数 (1) * 出现次数
total_shared_anions += num_shared * 1 * count
# 3. 计算平均值
# 平均每个目标原子参与的共享阴离子数 = 总的加权共享数 / 目标原子总数
average_sharing_per_atom = total_shared_anions / target_atom_count
return average_sharing_per_atom
def CS_count_old(struct, shared_count, sp='Li'):
count = 0
for site in struct.sites:
if site.species.chemical_system == 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
def check_only_corner_sharing(sharing_results: Dict[str, Dict[str, int]]) -> int:
"""
检查目标原子(sp)是否只参与了角共享共享1个阴离子
该函数是 calculate_polyhedra_sharing 的配套函数。
参数:
sharing_results (dict): 来自 calculate_polyhedra_sharing 函数的输出结果。
返回:
int:
- 1: 如果 sp 的共享关系中,边共享(2)、面共享(3)等数量均为0
并且至少存在一个角共享(1)。
- 0: 如果 sp 存在任何边、面等共享,或者没有任何共享关系。
"""
# 提取与目标原子 sp 相关的共享数据
sp_vs_sp_counts = sharing_results.get("sp_vs_sp", {})
sp_vs_other_counts = sharing_results.get("sp_vs_other", {})
# 1. 检查是否存在任何边共享、面共享等 (共享数 > 1)
# 检查 sp-sp 的共享
for num_shared_str, count in sp_vs_sp_counts.items():
if int(num_shared_str) > 1 and count > 0:
return 0 # 发现了边/面共享,立即返回 0
# 检查 sp-other 的共享
for num_shared_str, count in sp_vs_other_counts.items():
if int(num_shared_str) > 1 and count > 0:
return 0 # 发现了边/面共享,立即返回 0
# 2. 检查是否存在至少一个角共享 (共享数 == 1)
# 运行到这里,说明已经没有任何边/面共享了。
# 现在需要确认是否真的存在角共享,而不是完全没有共享。
corner_share_sp_sp = sp_vs_sp_counts.get("1", 0) > 0
corner_share_sp_other = sp_vs_other_counts.get("1", 0) > 0
if corner_share_sp_sp or corner_share_sp_other:
return 1 # 确认只存在角共享
else:
return 0 # 没有任何共享关系,也返回 0
# structure = Structure.from_file("../data/0921/wjy_001.cif")
# a = CS_catulate(structure,notice=True)
# b = CS_count(structure,a)
# print(f"{a}\n{b}")
# print(check_only_corner_sharing(a))