diff --git a/.idea/Screen.iml b/.idea/Screen.iml index 9bb2e78..909438d 100644 --- a/.idea/Screen.iml +++ b/.idea/Screen.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/py/CSM_reconstruct.py b/py/CSM_reconstruct.py new file mode 100644 index 0000000..8a59274 --- /dev/null +++ b/py/CSM_reconstruct.py @@ -0,0 +1,224 @@ +import os +import sys +import numpy as np +import argparse +from tqdm import tqdm +from scipy.spatial import ConvexHull +from pymatgen.core import Structure +from pymatgen.core.periodic_table import Element +from pymatgen.analysis.chemenv.coordination_environments.coordination_geometry_finder import LocalGeometryFinder + +# ================= 配置区域 ================= +# 建议使用绝对路径,避免找不到文件夹 +INPUT_DIR = "../../solidstate-tools/corner-sharing/data/1209/input" # 请确保这里有你的 .cif 文件 +OUTPUT_DIR = "../output/CSM" +TARGET_ELEMENT = 'Li' +ENV_TYPE = 'both' + + +# =========================================== + +class HiddenPrints: + '''用于隐藏 pymatgen 繁杂的输出''' + + def __enter__(self): + self._original_stdout = sys.stdout + sys.stdout = open(os.devnull, 'w') + + def __exit__(self, exc_type, exc_val, exc_tb): + sys.stdout.close() + sys.stdout = self._original_stdout + + +def non_elements(struct): + """ + 【关键修复】保留卤素(F, Cl, Br, I) 和其他阴离子,防止氯化物结构被清空。 + """ + # 这里加入了 F, Cl, Br, I, P, Se, Te 等 + anions_to_keep = {"O", "S", "N", "F", "Cl", "Br", "I", "P", "Se", "Te", "As", "Sb", "C"} + stripped = struct.copy() + 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 + + +def site_env(coord, struct, sp="Li", envtype='both'): + stripped = non_elements(struct) + + # 如果剥离后结构为空(例如纯金属锂),直接返回 + if len(stripped) == 0: + return {'csm': np.nan, 'vol': np.nan, 'type': 'Error_NoAnions'} + + with_li = stripped.copy() + # 插入一个探测用的 Li 原子 + with_li.append(sp, coord, coords_are_cartesian=False, validate_proximity=False) + + # 尝试排序,如果因为部分占据导致排序失败,则使用原始顺序 + try: + with_li = with_li.get_sorted_structure() + except: + pass + + tet_oct_competition = [] + + # ---------------- 四面体 (Tet) 检测 ---------------- + if envtype == 'both' or envtype == 'tet': + for dist in np.linspace(1, 4, 601): # 扫描距离 1A 到 4A + neigh = with_li.get_neighbors(with_li.sites[0], dist) + if len(neigh) < 4: + continue + elif len(neigh) > 4: + break + + neigh_coords = [i.coords for i in neigh] + try: + with HiddenPrints(): + lgf = LocalGeometryFinder(only_symbols=["T:4"]) + lgf.setup_structure(structure=with_li) + lgf.setup_local_geometry(isite=0, coords=neigh_coords) + + site_volume = ConvexHull(neigh_coords).volume + # 获取 CSM + csm_val = lgf.get_coordination_symmetry_measures()['T:4']['csm'] + + tet_env = {'csm': csm_val, 'vol': site_volume, 'type': 'tet'} + tet_oct_competition.append(tet_env) + except Exception: + pass + if len(neigh) == 4: break + + # ---------------- 八面体 (Oct) 检测 ---------------- + if envtype == 'both' or envtype == 'oct': + for dist in np.linspace(1, 4, 601): + neigh = with_li.get_neighbors(with_li.sites[0], dist) + if len(neigh) < 6: + continue + elif len(neigh) > 6: + break + + neigh_coords = [i.coords for i in neigh] + try: + with HiddenPrints(): + lgf = LocalGeometryFinder(only_symbols=["O:6"], permutations_safe_override=False) + lgf.setup_structure(structure=with_li) + lgf.setup_local_geometry(isite=0, coords=neigh_coords) + + site_volume = ConvexHull(neigh_coords).volume + csm_val = lgf.get_coordination_symmetry_measures()['O:6']['csm'] + + oct_env = {'csm': csm_val, 'vol': site_volume, 'type': 'oct'} + tet_oct_competition.append(oct_env) + except Exception: + pass + if len(neigh) == 6: break + + # ---------------- 结果判定 ---------------- + if len(tet_oct_competition) == 0: + return {'csm': np.nan, 'vol': np.nan, 'type': 'Non_' + envtype} + elif len(tet_oct_competition) == 1: + return tet_oct_competition[0] + elif len(tet_oct_competition) >= 2: + return min(tet_oct_competition, key=lambda x: x['csm']) + + +def extract_sites(struct, sp="Li", envtype='both'): + envlist = [] + # 遍历所有位点寻找 Li + for i, site in enumerate(struct): + site_elements = [el.symbol for el in site.species.elements] + if sp in site_elements: + try: + # 传入结构副本以防修改原结构 + singleenv = site_env(site.frac_coords, struct.copy(), sp, envtype) + envlist.append({ + 'site_index': i, + 'frac_coords': site.frac_coords, + 'type': singleenv.get('type', 'unknown'), + 'csm': singleenv.get('csm', np.nan), + 'volume': singleenv.get('vol', np.nan) + }) + except Exception as e: + # 捕捉单个位点计算错误,不中断程序 + # print(f" [Warn] Site {i} calculation failed: {e}") + pass + return envlist + + +def export_envs(envlist, sp, envtype, fname): + with open(fname, 'w') as f: + f.write('List of environment information\n') + f.write(f'Species : {sp}\n') + f.write(f'Envtype : {envtype}\n') + for item in envlist: + # 格式化输出,确保没有数据也能看懂 + f.write(f"Site index {item['site_index']}: {item}\n") + + +# ================= 主程序 ================= +def run_csm_analysis(): + # 1. 检查目录 + if not os.path.exists(INPUT_DIR): + print(f"错误: 输入目录不存在 -> {os.path.abspath(INPUT_DIR)}") + return + + cif_files = [] + for root, dirs, files in os.walk(INPUT_DIR): + for file in files: + if file.endswith(".cif"): + cif_files.append(os.path.join(root, file)) + + if not cif_files: + print(f"在 {INPUT_DIR} 中未找到 .cif 文件。") + return + + print(f"开始分析 {len(cif_files)} 个文件 (目标元素: {TARGET_ELEMENT}, 包含阴离子: F,Cl,Br,I,O,S,N...)") + + success_count = 0 + + for cif_path in tqdm(cif_files, desc="Calculating CSM"): + try: + # 准备路径 + rel_path = os.path.relpath(cif_path, INPUT_DIR) + rel_dir = os.path.dirname(rel_path) + file_base = os.path.splitext(os.path.basename(cif_path))[0] + + target_dir = os.path.join(OUTPUT_DIR, rel_dir) + if not os.path.exists(target_dir): + os.makedirs(target_dir) + + target_dat_path = os.path.join(target_dir, f"{file_base}.dat") + + # 如果文件已存在且不为空,可选择跳过 + # if os.path.exists(target_dat_path) and os.path.getsize(target_dat_path) > 0: + # continue + + # 读取结构 + struct = Structure.from_file(cif_path) + + # 检查是否含 Li + if Element(TARGET_ELEMENT) not in struct.composition.elements: + continue + + # 计算环境 + env_list = extract_sites(struct, sp=TARGET_ELEMENT, envtype=ENV_TYPE) + + # 写入结果 (即使 env_list 为空也写入一个标记文件,方便debug) + if env_list: + export_envs(env_list, sp=TARGET_ELEMENT, envtype=ENV_TYPE, fname=target_dat_path) + success_count += 1 + else: + with open(target_dat_path, 'w') as f: + f.write(f"No {TARGET_ELEMENT} environments found (Check connectivity or anion types).") + + except Exception as e: + print(f"\n[Error] File: {os.path.basename(cif_path)} -> {e}") + continue + + print(f"\n分析完成!成功生成 {success_count} 个文件。") + print(f"输出目录: {os.path.abspath(OUTPUT_DIR)}") + + +if __name__ == "__main__": + run_csm_analysis() \ No newline at end of file diff --git a/py/csm.py b/py/csm.py index b505b05..18f3920 100644 --- a/py/csm.py +++ b/py/csm.py @@ -12,11 +12,11 @@ from tqdm import tqdm # ================= 配置区域 ================= # 输入目录:使用筛选后的目录,只计算符合要求的材料 -INPUT_DIR = "../data/after_screening" +INPUT_DIR = "../../solidstate-tools/corner-sharing/data/1209/input" # 输出目录 OUTPUT_DIR = "../output/CSM" # 分析参数 -TARGET_ELEMENT = 'Li' +TARGET_ELEMENT = 'Na' ENV_TYPE = 'both' # 可选 'tet', 'oct', 'both' # =========================================== diff --git a/py/step1.py b/py/step1.py index cde8929..cb633fc 100644 --- a/py/step1.py +++ b/py/step1.py @@ -110,4 +110,4 @@ def read_files_check_basic(folder_path): if __name__ == "__main__": # 根据你的 readme,MP数据在 input_pre,ICSD在 input # 这里默认读取 input,你可以根据实际情况修改 - read_files_check_basic("../data/input") \ No newline at end of file + read_files_check_basic("../../solidstate-tools/corner-sharing/data/1209/input") \ No newline at end of file diff --git a/py/step1_direct.py b/py/step1_direct.py index b546626..051326c 100644 --- a/py/step1_direct.py +++ b/py/step1_direct.py @@ -96,7 +96,7 @@ def organize_files_direct(input_folder, output_base): if __name__ == "__main__": # 输入路径 - input_dir = "../data/input" # 如果是MP数据请改为 ../data/input_pre + input_dir = "../../solidstate-tools/corner-sharing/data/1209/input" # 如果是MP数据请改为 ../data/input_pre # 输出路径 output_dir = "../data/after_step1" diff --git a/py/utils/__init__.py b/py/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/utils/analyze_env_st.py b/py/utils/analyze_env_st.py index 48666bc..2526978 100644 --- a/py/utils/analyze_env_st.py +++ b/py/utils/analyze_env_st.py @@ -52,7 +52,7 @@ def non_elements(struct, sp='Li'): sp : the mobile specie returns a new structure containing only the framework anions (O, S, N). """ - anions_to_keep = {"O", "S", "N"} + anions_to_keep = {"O", "S", "N","Br","Cl"} stripped = struct.copy() species_to_remove = [el.symbol for el in stripped.composition.elements if el.symbol not in anions_to_keep] diff --git a/tool/Br.yaml b/tool/Li/Br.yaml similarity index 100% rename from tool/Br.yaml rename to tool/Li/Br.yaml diff --git a/tool/Cl.yaml b/tool/Li/Cl.yaml similarity index 100% rename from tool/Cl.yaml rename to tool/Li/Cl.yaml diff --git a/tool/O.yaml b/tool/Li/O.yaml similarity index 100% rename from tool/O.yaml rename to tool/Li/O.yaml diff --git a/tool/S.yaml b/tool/Li/S.yaml similarity index 100% rename from tool/S.yaml rename to tool/Li/S.yaml