From 6f304a634cae9a3d76637b2655906a63a6e841de Mon Sep 17 00:00:00 2001 From: flt6 <1404262047@qq.com> Date: Sun, 11 Jan 2026 11:43:35 +0800 Subject: [PATCH] add README in calc utils --- calc_utils/README.md | 73 ++++++++++++++++ calc_utils/bin/gsub | 176 +++++++++++++++++++++++++++++++++++++++ calc_utils/bin/gsub_wait | 116 ++++++++++++++++++++++++++ 3 files changed, 365 insertions(+) create mode 100644 calc_utils/bin/gsub create mode 100644 calc_utils/bin/gsub_wait diff --git a/calc_utils/README.md b/calc_utils/README.md index e69de29..7454f4a 100644 --- a/calc_utils/README.md +++ b/calc_utils/README.md @@ -0,0 +1,73 @@ +# calc-utils + +个人使用的计算化学工具集,主要基于 [ASE (Atomic Simulation Environment)](https://wiki.fysik.dtu.dk/ase/) 和 [RDKit](https://www.rdkit.org/)。 + +包含了一些方便的转换工具,以及针对特定服务器环境(PBS/Slurm/Custom)定制的 `ase.calculators.gaussian` 补丁。 + +专用软件,`futils.gaussian`在不同服务器环境中无法直接运行,必须予以修改。 + +## 安装 + +需要 Python 3.12+。 + +```bash +git clone https://github.com/your-repo/calc-utils.git +cd calc-utils +pip install . +``` + +## 功能模块 + +### 1. `futils.gaussian` (**Breaking Change**) + +这是一个对 `ase.calculators.gaussian` 的深度定制和 Monkey Patch。 + +**注意:导入此模块会直接修改 `ase.calculators.gaussian` 中的类定义。** + +主要修改内容: +- **强制任务提交脚本**:计算器的 `command` 默认被设置为调用 `gsub_wait` 脚本。 + - 默认路径硬编码为 `/home/fanhj/calcs/lele/tools/gsub_wait`(需要在 `futils/gaussian.py` 中按需修改 `GSUB` 变量)。 +- **文件后缀变更**:输入文件使用 `.gin` 而非 `.gjf`,输出文件默认读取 `.out`。 +- **参数增强**:`__init__` 方法提供了更详细的 Type Hinting 和默认参数(如 `mem="30GB"`, `proc=32`)。 +- **辅助方法**:增加了 `mod()` 方法用于快速复制并修改计算器参数。 + +```python +from futils.gaussian import Gaussian +from ase import Atoms + +# 使用定制后的 Gaussian 计算器 +# 注意:这会尝试调用 gsub_wait 提交任务 +calc = Gaussian(label='test_calc', method='B3LYP', basis='6-31G(d)') +``` + +### 2. `futils.rdkit2ase` + +提供 RDKit 分子对象 (`rdkit.Chem.Mol`) 与 ASE 原子对象 (`ase.Atoms`) 之间的无缝转换,**保留 3D 坐标**。 + +```python +from futils.rdkit2ase import MolToAtoms, AtomsToMol +from rdkit import Chem + +# RDKit -> ASE +mol = Chem.MolFromMolFile("molecule.mol") +atoms = MolToAtoms(mol) + +# ASE -> RDKit +new_mol = AtomsToMol(atoms) +``` + +### 3. `futils.rdkit_utils` + +一些 RDKit 绘图辅助函数。 +- `draw2D(mol)`: 生成 SVG 格式的 2D 分子图。 +- `draw3D(mol)`: 使用 IPythonConsole 绘制 3D 分子图。 + +## 脚本工具 (`bin/`) + +本项目包含了一些用于任务提交管理的 Shell 脚本,适用于特定的集群环境。 + +- **`gsub`**: 任务提交脚本。支持本地或通过 SSH 远程提交到名为 `cluster` 的主机。 +- **`gsub_wait`**: 提交任务并阻塞等待完成,用于 ASE Calculator 的 `command` 调用,以便实现 Python 脚本的同步执行。 + +**配置说明**: +使用前请检查 `bin/` 下的脚本以及 `futils/gaussian.py` 中的 `GSUB` 路径,根据您的服务器环境进行调整。 diff --git a/calc_utils/bin/gsub b/calc_utils/bin/gsub new file mode 100644 index 0000000..d781df8 --- /dev/null +++ b/calc_utils/bin/gsub @@ -0,0 +1,176 @@ +#!/bin/bash +set -u + +# Usage: gsub + +job=${1:-} +if [[ -z "$job" ]]; then + echo "Usage: $0 " + exit 1 +fi + +# ========================================== +# 0. 安全检测函数 (Safety Check) +# ========================================== +check_dangerous_path() { + local path="${1:-}" + + # 1. Empty check + if [[ -z "$path" ]]; then + echo "Error: Empty path is dangerous for deletion." >&2 + return 1 + fi + + # 2. Root check + if [[ "$path" == "/" ]]; then + echo "Error: Root path '/' is dangerous for deletion." >&2 + return 1 + fi + + # 3. Space check (optional, but good for safety) + if [[ "$path" =~ ^[[:space:]]+$ ]]; then + echo "Error: Whitespace path is dangerous." >&2 + return 1 + fi + + return 0 +} + +# ========================================== +# 1. 检查运行环境 (Check Host) +# ========================================== +# 如果不是 cluster,尝试通过 SSH 远程调用 +host_short=$(hostname -s 2>/dev/null || hostname) +if [[ "$host_short" != "cluster" ]]; then + # 假设本地挂载路径 /mnt/home 对应远程 /home (根据原脚本逻辑调整) + cur_dir=$(pwd) + remote_dir="${cur_dir//\/mnt\/home/\/home}" + + # 定位当前脚本并转换为远程路径 + # 获取脚本所在目录的绝对路径 + script_dir=$(cd "$(dirname "$0")" && pwd) + script_name=$(basename "$0") + local_script="$script_dir/$script_name" + + # 同样对脚本路径进行替换 + remote_script="${local_script//\/mnt\/home/\/home}" + + # 尝试在远程执行自己 + echo "Running remotely on cluster: $remote_script" >&2 + ssh cluster "cd '$remote_dir' && '$remote_script' '$job'" + exit $? +fi + +# ========================================== +# 2. 准备作业 (Prepare Job) +# ========================================== + +gin_file="$job.gin" +if [[ ! -f "$gin_file" ]]; then + echo "Error: $gin_file not found in $(pwd)" + exit 2 +fi + +# 解析配置确定资源 (Parse Proc) +# 查找 %NProcShared=XX +proc=$(sed -n 's/^%NProcShared=\([0-9]\+\).*$/\1/pI' "$gin_file" | head -n 1) + +queue="" +ppn="" + +if [[ "$proc" == "32" ]]; then + queue="n32" + ppn="32" +elif [[ "$proc" == "20" ]]; then + queue="n20" + ppn="20" +else + echo "Error: Unsupported NProcShared=$proc in $gin_file. Only 20 or 32 allowed." + exit 1 +fi + +# 清理旧文件 (Clean up old output) +if [[ -f "$job.out" ]]; then + # 原脚本逻辑:休眠并删除 + # echo "Warning: $job.out exists. Deleting..." >&2 + # 使用安全检查 + if check_dangerous_path "$job.out"; then + rm "$job.out" + else + echo "Skipping deletion of unsafe path: $job.out" >&2 + exit 1 + fi +fi + +# ========================================== +# 3. 生成作业脚本 (.job) +# ========================================== +job_file="$job.job" + +# 使用 heredoc 动态生成 PBS 脚本 +# 整合了原 g16_32.pbs 的内容和 gsub32 的追加内容 +cat > "$job_file" < $job.out" +g16 < $gin_file > $job.out + +echo "--------------------------------------------------------" +echo " The job was finished at \`date\`" +echo "--------------------------------------------------------" + +# Delete the tmp File (Cleanup Scratch) +echo "Cleaning up \$GAUSS_SCRDIR" +if check_rm_path "\$GAUSS_SCRDIR"; then + rm -rf "\$GAUSS_SCRDIR" +fi + +EOF + +# ========================================== +# 4. 提交作业 (Submit) +# ========================================== +# qsub 会输出 Job ID,例如 12345.cluster +qsub "$job_file" diff --git a/calc_utils/bin/gsub_wait b/calc_utils/bin/gsub_wait new file mode 100644 index 0000000..0a052d2 --- /dev/null +++ b/calc_utils/bin/gsub_wait @@ -0,0 +1,116 @@ +#!/bin/bash +set -u + +# Usage: gsub_wait + +job=${1:-} +if [[ -z "$job" ]]; then + echo "Usage: $0 " + exit 1 +fi + +# ========================================== +# 1. 提交任务 (Submit Job) +# ========================================== + +# 确定 gsub 命令位置 +# 优先查找当前目录下的 gsub,否则查找 PATH +if [[ -x "./gsub" ]]; then + GSUB_CMD="./gsub" +else + GSUB_CMD="gsub" +fi + +# 调用 gsub 并捕获输出 +# 注意:gsub 内部可能通过 SSH 在远程执行,最终返回 qsub 的输出 +output=$($GSUB_CMD "$job") +echo "$output" + +# ========================================== +# 2. 检查是否需要等待 (Check Silent Mode) +# ========================================== +# 如果 GSUB_SILENT 为 1,则不进行监控,直接退出 +if [[ "${GSUB_SILENT:-0}" == "1" ]]; then + exit 0 +fi + +# ========================================== +# 3. 监控任务进度 (Monitor Progress) +# ========================================== + +# 尝试提取 Job ID (例如: 67147.cluster -> 67147) +jobid_full=$(echo "$output" | grep -oE '[0-9]+\.cluster|[0-9]+' | head -n 1 || true) + +if [[ -n "$jobid_full" ]]; then + jobid=${jobid_full%%.*} + + # 准备参数 + out_file="$job.out" + gin_file="$job.gin" + end_file="$job.job.o$jobid" + + if [[ ! -f "$gin_file" ]]; then + # 如果 gin 文件找不到(可能是远程路径问题?),跳过监控 + echo "Warning: $gin_file not found nearby. Skipping monitor." + exit 0 + fi + + # 计算 Total Steps: (--link1-- 数量) + 1 + link_count=$(grep -c -- "--link1--" "$gin_file" || true) + total=$((link_count + 1)) + cntDone=0 + cntSCF=0 + + last_lines=0 + + echo "Monitoring Job $jobid..." + + while true; do + # A. 检查 PBS 结束文件 (Job 完成标志) + if [[ -f "$end_file" ]]; then + echo "Job finished (found $end_file)." + break + fi + + # B. 检查并读取 .out 输出文件 + if [[ -f "$out_file" ]]; then + curr_lines=$(wc -l < "$out_file" 2>/dev/null || echo 0) + + # 如果文件变小(被截断或重新生成),重置读取位置 + if (( curr_lines < last_lines )); then last_lines=0; fi + + if (( curr_lines > last_lines )); then + # 逐行处理新增内容 + # 使用进程替换 < <(...) 避免管道导致的子shell变量丢失问题 + while IFS= read -r line; do + + # 检查 SCF Done + # 正则匹配: SCF Done: ... E ... = (数值) A.U. + if [[ "$line" =~ SCF[[:space:]]Done:.*E.*=[[:space:]]*([-0-9.]+)[[:space:]]*A\.U\. ]]; then + energy="${BASH_REMATCH[1]}" + cntSCF=$((cntSCF + 1)) + echo "$job: SCF Done: $energy [$cntSCF] ($cntDone/$total)" + fi + + # 检查 Termination + if [[ "$line" == *"termination of Gaussian"* ]]; then + cntDone=$((cntDone + 1)) + echo "$job: task done ($cntDone/$total)" + fi + + done < <(tail -n "+$((last_lines + 1))" "$out_file") + + last_lines=$curr_lines + fi + fi + + sleep 2 + done + + # C. 最终校验 + if (( cntDone != total )); then + echo "Warning: cntDone ($cntDone) != total ($total)" + fi +else + echo "Could not parse Job ID from output. Monitor skipped." +fi