diff --git a/corner-sharing/utils/CS_analyse.py b/corner-sharing/utils/CS_analyse.py new file mode 100644 index 0000000..87a60e5 --- /dev/null +++ b/corner-sharing/utils/CS_analyse.py @@ -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}") \ No newline at end of file diff --git a/corner-sharing/utils/analyze_env_st.py b/corner-sharing/utils/analyze_env_st.py index ab7f5db..6275216 100644 --- a/corner-sharing/utils/analyze_env_st.py +++ b/corner-sharing/utils/analyze_env_st.py @@ -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") \ No newline at end of file +# struct = Structure.from_file("../data/0921/wjy_475.cif") +# site_info = extract_sites(struct, envtype="both") +# export_envs(site_info, sp="Li", envtype="both") \ No newline at end of file diff --git a/data_get/data_get.py b/data_get/data_get.py new file mode 100644 index 0000000..b3078e1 --- /dev/null +++ b/data_get/data_get.py @@ -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' + ) \ No newline at end of file diff --git a/data_get/input/cif_dataset.xlsx b/data_get/input/cif_dataset.xlsx new file mode 100644 index 0000000..1451784 Binary files /dev/null and b/data_get/input/cif_dataset.xlsx differ