calc—_v1
This commit is contained in:
56
main.sh
56
main.sh
@@ -1,30 +1,64 @@
|
||||
#!/bin/bash
|
||||
#修改权限
|
||||
|
||||
# =====================
|
||||
# 项目自动化筛选脚本
|
||||
# =====================
|
||||
|
||||
# 1. 初始化设置
|
||||
# 修改上一级目录权限
|
||||
chmod -R u+w ../Screen
|
||||
#启用screen环境
|
||||
|
||||
# 启用 screen 环境 (Python 3.11)
|
||||
source $(conda info --base)/etc/profile.d/conda.sh
|
||||
conda activate screen
|
||||
|
||||
# 设置当前目录为 PYTHONPATH
|
||||
cd py/
|
||||
export PYTHONPATH=$(pwd):$PYTHONPATH
|
||||
#调用预筛选
|
||||
|
||||
echo "============ Stage 1: Pre-process & Basic Screening ============"
|
||||
# 调用预筛选 (处理 input_pre 到 input)
|
||||
python pre_process.py
|
||||
|
||||
# 调用第一步筛选
|
||||
# 功能:读取 CIF,进行基础检查,按阴离子分类,创建独立文件夹 (Anion/ID/ID.cif)
|
||||
python step1.py
|
||||
|
||||
#为第一步筛出的所有材料制作脚本
|
||||
# 为所有材料生成 Zeo++ 分析脚本
|
||||
# 功能:生成 analyze.sh,输出重定向至 log.txt
|
||||
python make_sh.py
|
||||
#调整环境
|
||||
|
||||
# 2. 切换环境运行 Zeo++
|
||||
echo "============ Stage 2: Zeo++ Calculations ============"
|
||||
conda deactivate
|
||||
conda activate zeo
|
||||
#运行脚本
|
||||
|
||||
# 进入数据目录执行所有生成的 shell 脚本
|
||||
cd ../data/after_step1
|
||||
if [ -f "sh_all.sh" ]; then
|
||||
source sh_all.sh
|
||||
rm sh_all.sh
|
||||
#调整conda回到screen
|
||||
else
|
||||
echo "Error: sh_all.sh not found!"
|
||||
fi
|
||||
|
||||
# 3. 后处理与筛选 (Step 2-4)
|
||||
echo "============ Stage 3: Data Extraction & Advanced Screening ============"
|
||||
# 切回 screen 环境
|
||||
conda deactivate
|
||||
conda activate screen
|
||||
|
||||
#启用不同的python文件做分析
|
||||
cd ../../py
|
||||
python step2-5-file_process.py
|
||||
#python step6.py
|
||||
|
||||
# 提取日志数据
|
||||
# 功能:遍历所有 log.txt,提取孔径、距离等参数,生成汇总 CSV 到 ../output
|
||||
python extract_data.py
|
||||
|
||||
# 联合筛选 (Step 2, 3, 4)
|
||||
# 功能:读取 CSV,根据阈值筛选,将符合条件的材料软链接到 ../data/after_screening
|
||||
python step2_4_combined.py
|
||||
|
||||
# Step 5 (扩胞与实际检查)
|
||||
# 注意:这一步目前尚未更新适配新的软链接结构,待后续处理
|
||||
# python step5.py
|
||||
|
||||
echo "Done! Check results in ../data/after_screening"
|
||||
154
py/extract_data.py
Normal file
154
py/extract_data.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import os
|
||||
import re
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def extract_parameters_from_log(log_path):
|
||||
"""
|
||||
从 log.txt 中提取三个关键参数。
|
||||
如果未找到,返回 None。
|
||||
"""
|
||||
if not os.path.exists(log_path):
|
||||
return None, None, None
|
||||
|
||||
with open(log_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 正则表达式定义
|
||||
# 1. Percolation diameter (原来的 Step 2)
|
||||
# 匹配模式: # Percolation diameter (A): 1.06
|
||||
re_percolation = r"Percolation diameter \(A\):\s*([\d\.]+)"
|
||||
|
||||
# 2. Minimum of d (原来的 Step 3)
|
||||
# 匹配模式: the minium of d \n 3.862140561244235
|
||||
re_min_d = r"the minium of d\s*\n\s*([\d\.]+)"
|
||||
|
||||
# 3. Maximum node length (原来的 Step 4)
|
||||
# 匹配模式: # Maximum node length detected: 1.332 A
|
||||
re_max_node = r"Maximum node length detected:\s*([\d\.]+)\s*A"
|
||||
|
||||
# 提取数据
|
||||
match_perc = re.search(re_percolation, content)
|
||||
match_d = re.search(re_min_d, content)
|
||||
match_node = re.search(re_max_node, content)
|
||||
|
||||
# 获取值,如果没匹配到则为空字符串或None
|
||||
val_perc = match_perc.group(1) if match_perc else None
|
||||
val_d = match_d.group(1) if match_d else None
|
||||
val_node = match_node.group(1) if match_node else None
|
||||
|
||||
return val_perc, val_d, val_node
|
||||
|
||||
|
||||
def process_folder_recursively(base_input_folder, base_output_folder):
|
||||
"""
|
||||
递归遍历文件夹,提取数据并生成 CSV。
|
||||
逻辑:
|
||||
1. 遍历 base_input_folder 下的第一层子文件夹(通常是阴离子类别,如 O, S, O+S 等)。
|
||||
2. 如果是单阴离子(如 O),直接处理其下的材料文件夹。
|
||||
3. 如果是混合阴离子(如 O+S),需要进入下一层(如 O+S/O 和 O+S/S),分别处理。
|
||||
4. 结果保存在 base_output_folder 下保持相同的目录结构。
|
||||
"""
|
||||
|
||||
# 获取 after_step1 下的所有顶层目录 (例如 O, S, Cl, S+O ...)
|
||||
if not os.path.exists(base_input_folder):
|
||||
print(f"输入目录 {base_input_folder} 不存在")
|
||||
return
|
||||
|
||||
top_dirs = [d for d in os.listdir(base_input_folder) if os.path.isdir(os.path.join(base_input_folder, d))]
|
||||
|
||||
for top_dir in top_dirs:
|
||||
top_path = os.path.join(base_input_folder, top_dir)
|
||||
|
||||
# 判断是否是混合阴离子目录(名字包含 +)
|
||||
if "+" in top_dir:
|
||||
# 混合阴离子情况:例如 S+O
|
||||
# 需要遍历其子目录:S+O/S 和 S+O/O
|
||||
sub_anions = [d for d in os.listdir(top_path) if os.path.isdir(os.path.join(top_path, d))]
|
||||
for sub_anion in sub_anions:
|
||||
# 构建路径:../data/after_step1/S+O/S
|
||||
current_process_path = os.path.join(top_path, sub_anion)
|
||||
# 构建输出 CSV 路径:../output/S+O/S/S.csv (或者 S+O_S.csv,这里按你要求的 O+S/O/O.csv 格式)
|
||||
# 输出目录: ../output/S+O/S
|
||||
output_dir = os.path.join(base_output_folder, top_dir, sub_anion)
|
||||
csv_filename = f"{sub_anion}.csv"
|
||||
|
||||
extract_and_save(current_process_path, output_dir, csv_filename)
|
||||
else:
|
||||
# 单一阴离子情况:例如 O
|
||||
# 路径:../data/after_step1/O
|
||||
current_process_path = top_path
|
||||
# 输出目录: ../output/O
|
||||
output_dir = os.path.join(base_output_folder, top_dir)
|
||||
csv_filename = f"{top_dir}.csv"
|
||||
|
||||
extract_and_save(current_process_path, output_dir, csv_filename)
|
||||
|
||||
|
||||
def extract_and_save(input_dir, output_dir, csv_name):
|
||||
"""
|
||||
实际执行提取和保存的函数。
|
||||
input_dir: 包含各个材料文件夹的目录 (例如 .../O/)
|
||||
output_dir: CSV 保存目录
|
||||
csv_name: CSV 文件名
|
||||
"""
|
||||
data_list = []
|
||||
|
||||
# input_dir 下面应该是各个材料的文件夹,例如 141, 142 ...
|
||||
if not os.path.exists(input_dir):
|
||||
return
|
||||
|
||||
# 遍历下面的所有材料文件夹
|
||||
material_folders = [f for f in os.listdir(input_dir) if os.path.isdir(os.path.join(input_dir, f))]
|
||||
|
||||
print(f"正在处理目录: {input_dir}, 发现 {len(material_folders)} 个材料文件夹")
|
||||
|
||||
for material_id in material_folders:
|
||||
material_path = os.path.join(input_dir, material_id)
|
||||
# 根据新的 step1 逻辑,log 文件名为 log.txt
|
||||
log_path = os.path.join(material_path, "log.txt")
|
||||
|
||||
# 提取数据
|
||||
perc, min_d, max_node = extract_parameters_from_log(log_path)
|
||||
|
||||
# 只要有一个数据存在,就记录(或者你可以改为必须全部存在)
|
||||
# 这里设置为只要有记录就加入,方便排查错误
|
||||
if perc or min_d or max_node:
|
||||
data_list.append({
|
||||
"Filename": material_id,
|
||||
"Percolation Diameter (A)": perc,
|
||||
"Minimum of d": min_d,
|
||||
"Maximum Node Length (A)": max_node
|
||||
})
|
||||
else:
|
||||
# 如果 log.txt 不存在或者提取不到数据,可以选择记录空值
|
||||
data_list.append({
|
||||
"Filename": material_id,
|
||||
"Percolation Diameter (A)": None,
|
||||
"Minimum of d": None,
|
||||
"Maximum Node Length (A)": None
|
||||
})
|
||||
|
||||
# 如果有数据,保存为 CSV
|
||||
if data_list:
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
csv_path = os.path.join(output_dir, csv_name)
|
||||
df = pd.DataFrame(data_list)
|
||||
# 调整列顺序
|
||||
df = df[["Filename", "Percolation Diameter (A)", "Minimum of d", "Maximum Node Length (A)"]]
|
||||
|
||||
df.to_csv(csv_path, index=False)
|
||||
print(f"数据已保存至: {csv_path}")
|
||||
else:
|
||||
print(f"目录 {input_dir} 未提取到有效数据")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 输入基础路径 (假设数据在 step1 处理后)
|
||||
input_base = "../data/after_step1"
|
||||
# 输出基础路径 (你提到的 output 文件夹)
|
||||
output_base = "../output"
|
||||
|
||||
process_folder_recursively(input_base, output_base)
|
||||
@@ -1,63 +1,13 @@
|
||||
import os
|
||||
|
||||
|
||||
def creat_sh(input_folder, anion, sh_file_path='analyze.sh'):
|
||||
"""
|
||||
创建shell脚本,只处理两类CIF文件:
|
||||
1. 纯数字命名的CIF文件 (例如: 123.cif)
|
||||
2. 数字-坐标格式的CIF文件 (例如: 123-x1y2z3.cif)
|
||||
|
||||
参数:
|
||||
input_folder: 输入文件夹路径
|
||||
anion: 阴离子类型
|
||||
sh_file_path: 生成的shell脚本路径
|
||||
"""
|
||||
# 文件夹路径
|
||||
folder_path = input_folder
|
||||
|
||||
import re
|
||||
|
||||
# 定义两种文件名模式的正则表达式
|
||||
pattern1 = re.compile(r'^\d+\.cif$') # 纯数字.cif
|
||||
pattern2 = re.compile(r'^\d+-x\d+y\d+z\d+\.cif$') # 数字-x数字y数字z数字.cif
|
||||
|
||||
# 打开SH脚本文件用于写入
|
||||
with open(sh_file_path, 'w') as sh_file:
|
||||
# 写入脚本头部
|
||||
sh_file.write('#!/bin/bash\n')
|
||||
|
||||
# 遍历文件夹中的所有文件
|
||||
for filename in os.listdir(folder_path):
|
||||
file_path = os.path.join(folder_path, filename)
|
||||
|
||||
# 只处理文件(不处理文件夹)
|
||||
if os.path.isfile(file_path):
|
||||
# 检查文件名是否匹配两种模式之一
|
||||
if pattern1.match(filename) or pattern2.match(filename):
|
||||
# 生成对应的命令
|
||||
command = f"python ../../../tool/analyze_voronoi_nodes.py {filename} -i ../../../tool/{anion}.yaml > {filename}.txt\n"
|
||||
# 将命令写入SH脚本文件
|
||||
sh_file.write(command)
|
||||
|
||||
print(f"SH脚本已生成:{sh_file_path}")
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def create_sh_recursive(base_folder, tool_path="tool", relative_depth=2):
|
||||
"""
|
||||
递归遍历文件夹,为每个包含.cif文件的文件夹生成analyze.sh脚本,
|
||||
递归遍历文件夹,为每个包含.cif文件的文件夹生成analyze.sh脚本,
|
||||
并在基础文件夹下创建一个sh_all.sh来执行所有脚本。
|
||||
|
||||
参数:
|
||||
base_folder: 起始文件夹路径
|
||||
tool_path: 工具目录的基本路径
|
||||
relative_depth: 基础相对深度,用于计算正确的相对路径
|
||||
"""
|
||||
# 用于收集所有生成的analyze.sh脚本的相对路径
|
||||
analyze_sh_paths = []
|
||||
base_folder_name = os.path.basename(base_folder)
|
||||
|
||||
def process_folder(folder_path, current_depth=0):
|
||||
print(f"处理文件夹: {folder_path}")
|
||||
@@ -66,17 +16,31 @@ def create_sh_recursive(base_folder, tool_path="tool", relative_depth=2):
|
||||
folder_name = os.path.basename(folder_path)
|
||||
|
||||
# 检查当前文件夹是否包含.cif文件
|
||||
has_cif_files = any(
|
||||
f.endswith('.cif') for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f)))
|
||||
# 注意:这里我们只关心当前层级下的cif文件
|
||||
cif_files = [f for f in os.listdir(folder_path) if
|
||||
f.endswith('.cif') and os.path.isfile(os.path.join(folder_path, f))]
|
||||
has_cif_files = len(cif_files) > 0
|
||||
|
||||
# 如果当前文件夹包含.cif文件,生成脚本
|
||||
# 如果当前文件夹包含.cif文件,生成脚本
|
||||
if has_cif_files:
|
||||
# 计算正确的工具路径(根据深度增加../)
|
||||
# 计算正确的工具路径(根据深度增加../)
|
||||
dots = "../" * (relative_depth + current_depth)
|
||||
tool_relative_path = f"{dots}{tool_path}"
|
||||
|
||||
# 确定anion参数(使用文件夹名)
|
||||
# --- 修改开始: 修正Anion识别逻辑 ---
|
||||
# 如果结构是 Anion/MaterialID/file.cif,此时folder_name是MaterialID
|
||||
# 我们需要上一级目录的名字作为Anion (例如 'O', 'S', 'Cl') 来寻找对应的 .yaml 文件
|
||||
# 简单的判断逻辑:如果当前文件夹名字主要由数字组成(或者是ID格式),且包含cif文件,我们假设其父目录是Anion类型
|
||||
# 或者更直接的逻辑:在你的新结构中,包含cif的文件夹必定是底层文件夹,其父目录必定是Anion
|
||||
parent_dir_name = os.path.basename(os.path.dirname(folder_path))
|
||||
|
||||
# 这里做一个简单的保护,如果是在第一层(比如直接在O文件夹下有cif),保持原状,否则使用父目录名
|
||||
# 在新结构下,cif总是在 '.../O/141/141.cif',所以anion应该是 parent_dir_name ('O')
|
||||
if parent_dir_name in ['O', 'S', 'Cl', 'Br'] or folder_name not in ['O', 'S', 'Cl', 'Br']:
|
||||
anion = parent_dir_name
|
||||
else:
|
||||
anion = folder_name
|
||||
# --- 修改结束 ---
|
||||
|
||||
# 生成脚本文件路径
|
||||
sh_file_path = os.path.join(folder_path, "analyze.sh")
|
||||
@@ -84,18 +48,17 @@ def create_sh_recursive(base_folder, tool_path="tool", relative_depth=2):
|
||||
# 创建脚本
|
||||
with open(sh_file_path, 'w') as sh_file:
|
||||
sh_file.write('#!/bin/bash\n')
|
||||
for filename in os.listdir(folder_path):
|
||||
file_path = os.path.join(folder_path, filename)
|
||||
if os.path.isfile(file_path) and filename.endswith('.cif'):
|
||||
command = f"python {tool_relative_path}/analyze_voronoi_nodes.py {filename} -i {tool_relative_path}/{anion}.yaml > {filename}.txt\n"
|
||||
for filename in cif_files:
|
||||
# --- 修改开始: 输出重定向到 log.txt ---
|
||||
command = f"python {tool_relative_path}/analyze_voronoi_nodes.py {filename} -i {tool_relative_path}/{anion}.yaml > log.txt\n"
|
||||
# --- 修改结束 ---
|
||||
sh_file.write(command)
|
||||
|
||||
# 将此脚本添加到收集器中
|
||||
# 计算相对于基础文件夹的路径
|
||||
rel_path = os.path.relpath(folder_path, base_folder)
|
||||
analyze_sh_paths.append(rel_path)
|
||||
|
||||
print(f"生成脚本: {sh_file_path} (工具路径: {tool_relative_path})")
|
||||
print(f"生成脚本: {sh_file_path} (工具路径: {tool_relative_path}, Anion: {anion})")
|
||||
|
||||
# 获取子文件夹列表
|
||||
subdirs = [d for d in os.listdir(folder_path) if os.path.isdir(os.path.join(folder_path, d))]
|
||||
@@ -105,7 +68,7 @@ def create_sh_recursive(base_folder, tool_path="tool", relative_depth=2):
|
||||
elements = folder_name.split("+")
|
||||
for element in elements:
|
||||
element_dir = os.path.join(folder_path, element)
|
||||
# 如果对应元素的子文件夹不存在,创建它
|
||||
# 如果对应元素的子文件夹不存在,创建它
|
||||
if not os.path.exists(element_dir):
|
||||
os.makedirs(element_dir)
|
||||
print(f"创建子文件夹: {element_dir}")
|
||||
@@ -144,13 +107,8 @@ def create_sh_recursive(base_folder, tool_path="tool", relative_depth=2):
|
||||
# 修改权限使脚本可执行
|
||||
os.chmod(sh_all_path, 0o755)
|
||||
print(f"生成总执行脚本: {sh_all_path}")
|
||||
print("所有脚本生成完成!")
|
||||
# 示例调用
|
||||
# create_sh_recursive("../data/after_step1")
|
||||
print("所有脚本生成完成!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# creat_sh("../data/after_step1/O","O","../data/after_step1/O/analyze.sh")
|
||||
# creat_sh("../data/after_step1/S","S","../data/after_step1/S/analyze.sh")
|
||||
# creat_sh("../data/after_step1/Cl","Cl","../data/after_step1/Cl/analyze.sh")
|
||||
# creat_sh("../data/after_step1/Br","Br","../data/after_step1/Br/analyze.sh")
|
||||
create_sh_recursive("../data/after_step1")
|
||||
31
py/step1.py
31
py/step1.py
@@ -1,12 +1,11 @@
|
||||
from pymatgen.core import Structure
|
||||
from pymatgen.core.periodic_table import Element, Specie
|
||||
from pymatgen.io.cif import CifWriter
|
||||
|
||||
from crystal_2 import crystal
|
||||
import crystal_2
|
||||
import os
|
||||
import shutil
|
||||
from pymatgen.io.cif import CifWriter
|
||||
|
||||
|
||||
def read_files_check_basic(folder_path):
|
||||
file_contents = []
|
||||
@@ -24,25 +23,38 @@ def read_files_check_basic(folder_path):
|
||||
file_contents.append(temp)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
continue # 如果出错跳过当前循环,避免temp未定义报错
|
||||
|
||||
print(f"正在处理{filename}")
|
||||
temp.check_basic()
|
||||
|
||||
if temp.check_basic_result:
|
||||
# 获取不带后缀的文件名,用于创建同名文件夹
|
||||
file_base_name = os.path.splitext(filename)[0]
|
||||
|
||||
if not "+" in temp.anion:
|
||||
target_folder = os.path.join("../data/after_step1",f"{temp.anion}")
|
||||
# 单一阴离子情况
|
||||
# 路径变为: ../data/after_step1/Anion/FileBaseName/
|
||||
base_anion_folder = os.path.join("../data/after_step1", f"{temp.anion}")
|
||||
target_folder = os.path.join(base_anion_folder, file_base_name)
|
||||
|
||||
if not os.path.exists(target_folder):
|
||||
os.makedirs(target_folder)
|
||||
|
||||
# 目标文件路径
|
||||
target_file_path = os.path.join(target_folder, filename)
|
||||
|
||||
# 复制文件到目标文件夹
|
||||
shutil.copy(file_path, target_file_path)
|
||||
print(f"文件 {filename}通过基本筛选,已复制到 {target_folder}")
|
||||
print(f"文件 {filename}通过基本筛选,已复制到 {target_folder}")
|
||||
else:
|
||||
# 混合阴离子情况
|
||||
anions = temp.anion.split("+")
|
||||
for anion in anions:
|
||||
target_folder = os.path.join("../data/after_step1", f"{temp.anion}")
|
||||
target_folder = os.path.join(target_folder, anion)
|
||||
# 路径变为: ../data/after_step1/AnionCombination/Anion/FileBaseName/
|
||||
base_group_folder = os.path.join("../data/after_step1", f"{temp.anion}")
|
||||
base_anion_folder = os.path.join(base_group_folder, anion)
|
||||
target_folder = os.path.join(base_anion_folder, file_base_name)
|
||||
|
||||
if not os.path.exists(target_folder):
|
||||
os.makedirs(target_folder)
|
||||
|
||||
@@ -50,5 +62,8 @@ def read_files_check_basic(folder_path):
|
||||
target_file_path = os.path.join(target_folder, filename)
|
||||
# 复制文件到目标文件夹
|
||||
shutil.copy(file_path, target_file_path)
|
||||
print(f"文件 {filename}通过基本筛选,已复制到 {target_folder}")
|
||||
print(f"文件 {filename}通过基本筛选,已复制到 {target_folder}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
read_files_check_basic("../data/input")
|
||||
147
py/step2_4_combined.py
Normal file
147
py/step2_4_combined.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import os
|
||||
import pandas as pd
|
||||
import math
|
||||
|
||||
# ================= 配置区域 =================
|
||||
# 定义各阴离子的筛选阈值
|
||||
# perc: Percolation diameter (对应 Step 2, 大于此值)
|
||||
# min_d: Minimum of d (对应 Step 3, 小于此值)
|
||||
# node: Maximum node length (对应 Step 4, 大于此值)
|
||||
THRESHOLDS = {
|
||||
"O": {"perc": 0.50, "min_d": 3.0, "node": 2.2},
|
||||
"S": {"perc": 0.55, "min_d": 3.0, "node": 2.2},
|
||||
"Cl": {"perc": 0.45, "min_d": 3.0, "node": 2.0},
|
||||
"Br": {"perc": 0.45, "min_d": 3.0, "node": 2.0}
|
||||
}
|
||||
|
||||
# 路径配置
|
||||
CSV_ROOT_DIR = "../output" # CSV 所在的根目录
|
||||
DATA_SOURCE_DIR = "../data/after_step1" # 原始 CIF 文件所在的根目录 (用于创建链接源)
|
||||
TARGET_DIR = "../data/after_screening" # 筛选后放置软链接的目标目录
|
||||
|
||||
|
||||
# ===========================================
|
||||
|
||||
def check_requirements(row, anion_type):
|
||||
"""
|
||||
检查单行数据是否符合要求
|
||||
"""
|
||||
# 获取该阴离子类型的阈值配置
|
||||
config = THRESHOLDS.get(anion_type)
|
||||
if not config:
|
||||
print(f"Warning: 未知的阴离子类型 {anion_type},跳过筛选。")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 获取数值 (处理可能的空值或非数字情况)
|
||||
perc = float(row["Percolation Diameter (A)"])
|
||||
min_d = float(row["Minimum of d"])
|
||||
node = float(row["Maximum Node Length (A)"])
|
||||
|
||||
# 检查是否为 NaN
|
||||
if math.isnan(perc) or math.isnan(min_d) or math.isnan(node):
|
||||
return False
|
||||
|
||||
# --- 筛选逻辑 ---
|
||||
# Step 2: 连通孔径 > 阈值
|
||||
c1 = perc > config["perc"]
|
||||
# Step 3: 最短距离 < 3.0 (所有元素目前都是3.0)
|
||||
c2 = min_d < config["min_d"]
|
||||
# Step 4: 扩大锂离子节点 > 阈值
|
||||
c3 = node > config["node"]
|
||||
|
||||
return c1 and c2 and c3
|
||||
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def create_symlink(group_name, anion_name, material_id):
|
||||
"""
|
||||
创建软链接
|
||||
源: ../data/after_step1/Group/Anion/ID/ID.cif
|
||||
目: ../data/after_screening/Group/Anion/ID.cif
|
||||
"""
|
||||
# 1. 构建源文件路径 (必须使用绝对路径以确保软链接在任何地方都有效)
|
||||
# 注意:根据你修改后的 step1,文件在 ID 文件夹内,如 141/141.cif
|
||||
rel_source_path = os.path.join(DATA_SOURCE_DIR, group_name, anion_name, material_id, f"{material_id}.cif")
|
||||
abs_source_path = os.path.abspath(rel_source_path)
|
||||
|
||||
if not os.path.exists(abs_source_path):
|
||||
print(f"源文件不存在: {abs_source_path}")
|
||||
return
|
||||
|
||||
# 2. 构建目标文件夹路径
|
||||
target_subdir = os.path.join(TARGET_DIR, group_name, anion_name)
|
||||
if not os.path.exists(target_subdir):
|
||||
os.makedirs(target_subdir)
|
||||
|
||||
# 3. 构建目标链接路径
|
||||
target_link_path = os.path.join(target_subdir, f"{material_id}.cif")
|
||||
|
||||
# 4. 创建链接
|
||||
try:
|
||||
# 如果目标已经存在(可能是旧的链接),先删除
|
||||
if os.path.exists(target_link_path) or os.path.islink(target_link_path):
|
||||
os.remove(target_link_path)
|
||||
|
||||
os.symlink(abs_source_path, target_link_path)
|
||||
# print(f"Link: {material_id} -> Passed")
|
||||
except OSError as e:
|
||||
print(f"创建软链接失败 {material_id}: {e}")
|
||||
|
||||
|
||||
def process_all_csvs():
|
||||
"""
|
||||
遍历 output 文件夹下的所有 CSV 并处理
|
||||
"""
|
||||
if not os.path.exists(CSV_ROOT_DIR):
|
||||
print(f"CSV 目录不存在: {CSV_ROOT_DIR}")
|
||||
return
|
||||
|
||||
print("开始执行 Step 2-4 联合筛选...")
|
||||
|
||||
# 遍历 output 目录
|
||||
# 结构预期: ../output/Group/Anion/Anion.csv (例如 ../output/O+S/O/O.csv 或 ../output/O/O.csv)
|
||||
for root, dirs, files in os.walk(CSV_ROOT_DIR):
|
||||
for file in files:
|
||||
if file.endswith(".csv"):
|
||||
csv_path = os.path.join(root, file)
|
||||
|
||||
# 推断 Group 和 Anion
|
||||
# root 的末尾应该是 .../Group/Anion
|
||||
# 例如 root = ../output/O+S/O
|
||||
|
||||
path_parts = os.path.normpath(root).split(os.sep)
|
||||
# 倒数第一级是 Anion (O), 倒数第二级是 Group (O+S)
|
||||
if len(path_parts) >= 2:
|
||||
anion_name = path_parts[-1]
|
||||
group_name = path_parts[-2]
|
||||
else:
|
||||
print(f"跳过路径结构异常的 CSV: {csv_path}")
|
||||
continue
|
||||
|
||||
# 确保这是一个有效的阴离子类型
|
||||
if anion_name not in THRESHOLDS:
|
||||
continue
|
||||
|
||||
print(f"正在处理: Group={group_name}, Anion={anion_name} ({file})")
|
||||
|
||||
# 读取 CSV
|
||||
df = pd.read_csv(csv_path)
|
||||
|
||||
pass_count = 0
|
||||
total_count = len(df)
|
||||
|
||||
for index, row in df.iterrows():
|
||||
material_id = str(row['Filename'])
|
||||
|
||||
if check_requirements(row, anion_name):
|
||||
create_symlink(group_name, anion_name, material_id)
|
||||
pass_count += 1
|
||||
|
||||
print(f" - 完成: {pass_count}/{total_count} 个材料通过筛选并建立链接。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
process_all_csvs()
|
||||
113
readme.md
113
readme.md
@@ -1,71 +1,74 @@
|
||||
# 高通量筛选
|
||||
# 高通量筛选与扩胞项目
|
||||
|
||||
## 版本
|
||||
calc
|
||||
## 环境配置需求
|
||||
|
||||
## 配置需求
|
||||
项目需要配置两个 Conda 环境,名称分别为 **screen** 和 **zeo**。
|
||||
|
||||
需要两个conda环境,名字分别为**screen**,**zeo**
|
||||
### 1. zeo 环境 (用于几何结构分析)
|
||||
* **Python**: 2
|
||||
* **核心库**: `zeo++` (需编译), `pymatgen==2018.12.12`, `numpy==1.16.6`
|
||||
* **其他**: `os`, `argparse`, `PrettyTable`, `monty`, `future`
|
||||
|
||||
### zeo
|
||||
### 2. screen 环境 (用于逻辑筛选与数据处理)
|
||||
* **Python**: 3.11.4
|
||||
* **核心库**: `pymatgen==2024.11.13`, `pandas` (新增,用于处理CSV)
|
||||
|
||||
#### 运行库需求
|
||||
## 快速开始
|
||||
|
||||
``` 2018.12.12
|
||||
python == 2
|
||||
pymatgen == 2018.12.12
|
||||
Numpy = 1.16.6
|
||||
os
|
||||
argparse = 1.4.0
|
||||
PrettyTable = 1.01
|
||||
monty = 1.0.0
|
||||
future = 1.0.0
|
||||
1. **数据准备**:
|
||||
* 如果数据来源为 **Materials Project (MP)**,请将 CIF 文件放入 `data/input_pre`。
|
||||
* 如果数据来源为 **ICSD**,请直接将 CIF 文件放入 `data/input`。
|
||||
2. **运行**:
|
||||
* 确保已创建上述两个 Conda 环境。
|
||||
* 在根目录下运行自动化脚本:
|
||||
```bash
|
||||
bash main.sh
|
||||
```
|
||||
|
||||
#### zeo++软件需求
|
||||
## 处理流程详解
|
||||
|
||||
需要编译后放入python库中
|
||||
### Stage 1: 预处理与基础筛选 (Step 1)
|
||||
* **Pre-process**: 清洗数据,统一放入 `input` 文件夹。
|
||||
* **Step 1**:
|
||||
* 读取 CIF 文件,利用 `crystal_2` 库检查电荷平衡与化学式。
|
||||
* **文件重组**: 将通过筛选的文件按阴离子类型分类。
|
||||
* **新结构**: 每个材料拥有独立的文件夹(例如 `after_step1/O/141/141.cif`),便于管理后续的计算日志。
|
||||
* **Make SH**: 自动生成用于调用 Zeo++ 的 `analyze.sh` 脚本。
|
||||
|
||||
### screen
|
||||
### Stage 2: Zeo++ 计算
|
||||
* 切换至 `zeo` 环境。
|
||||
* 计算材料的孔径 (Percolation diameter)、比表面积等几何参数。
|
||||
* 结果输出为每个文件夹下的 **`log.txt`**。
|
||||
|
||||
```
|
||||
python == 3.11.4
|
||||
pymatgen == 2024.11.13
|
||||
```
|
||||
### Stage 3: 数据提取与联合筛选 (Step 2-4)
|
||||
* **数据提取 (`extract_data.py`)**:
|
||||
* 自动遍历所有文件夹中的 `log.txt`。
|
||||
* 提取关键参数:`Percolation Diameter` (Step 2), `Minimum of d` (Step 3), `Maximum Node Length` (Step 4)。
|
||||
* 结果汇总为 CSV 文件保存在 `output/` 目录下(例如 `output/O/O.csv`)。
|
||||
* **联合筛选 (`step2_4_combined.py`)**:
|
||||
* 读取 CSV 文件,根据预设的阈值(如 O: Perc>0.5, Min_d<3.0, Node>2.2)进行过滤。
|
||||
* **结果**: 将符合所有条件的材料,以**软链接**的形式汇聚到 `data/after_screening` 文件夹中。
|
||||
|
||||
## 使用说明
|
||||
---
|
||||
|
||||
如果配置的conda环境同名,运行**main.sh**即可
|
||||
## 扩胞逻辑 (Step 5 - 待后续执行)
|
||||
|
||||
当数据来源为MP时,需要将数据放在input_pre中
|
||||
目前扩胞逻辑维持原状,基于筛选后的结构进行处理。
|
||||
|
||||
如果数据来源为ICSD,仅需将数据放在input中即可
|
||||
### 算法分解
|
||||
1. **读取结构**: 解析 CIF 文件。
|
||||
2. **统计 Occupation**:
|
||||
* 将具有相同 Occupation 值的原子归为一类。
|
||||
* 生成 `Occupation_list` 字典。
|
||||
3. **计算扩大倍数**:
|
||||
* 根据 Occupation 的分子分母情况(如 0.5 对应 1/2),计算公约数。
|
||||
4. **生成结构列表**:
|
||||
* 根据分子与分母生成 `structure_list`。
|
||||
5. **对称性处理与扩胞**:
|
||||
* 根据材料结构的对称性,生成三个方向的扩胞列表 (如 `{"x":1, "y":2, "z":1}`)。
|
||||
6. **生成新文件**:
|
||||
* 结合 `structure_list` 与扩胞倍数生成最终的超胞 CIF。
|
||||
|
||||
|
||||
|
||||
# 扩胞
|
||||
## 以下为每一步的分解
|
||||
### Step1
|
||||
读取cif文件
|
||||
### Step2
|
||||
统计Occupation情况,将具有相同Occupation值的记为一类,用Occupation值作为Key创建字典,该字典的一个项为atom_serial,是一个列表,记录相同Ocupation值的原子序号
|
||||
将上述字典输入一个列表Occupation_list,字典预留分子与分母两个参数
|
||||
需要函数为
|
||||
```angular2html
|
||||
def process_cif_file(struct)
|
||||
return Occupation_list
|
||||
```
|
||||
### step3
|
||||
根据Occupation_list来计算扩大倍数\\
|
||||
首先逐一计算每个字典的分子与分母,根据key来计算,例如第一个key值为0.5,此时其对应分子为1,分母为2
|
||||
合并没一个字典,探索每一个分数的情况并求出公约数与对应的分子,更新每一个字典的值
|
||||
### step4
|
||||
根据分子与分母情况,生成structure_list,其中Occupation_list中的元素的number处的和为分子,总共个数为分母
|
||||
### step5
|
||||
根据材料结构决定对称性,对不同对称性得到不同等效情况
|
||||
根据对称性与最终扩胞生成三个方向扩胞列表,其中每个元素是字典,遵循格式为{["x":1,"y":2,"z":1]}
|
||||
### step5
|
||||
根据structure_list与Occupation_list生成新的cif并保存
|
||||
### 一些假设
|
||||
只考虑两个原子在同一位置上,暂不考虑三个原子以上的情况
|
||||
不考虑Li原子的共占位情况,对Li原子不做处理
|
||||
### 假设条件
|
||||
* 只考虑两个原子在同一位置上的共占位情况。
|
||||
* 不考虑 Li 原子的共占位情况,对 Li 原子不做处理。
|
||||
Reference in New Issue
Block a user