ion-CSP 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ion_CSP/__init__.py +8 -0
 - ion_CSP/app.py +201 -0
 - ion_CSP/convert_SMILES.py +291 -0
 - ion_CSP/empirical_estimate.py +505 -0
 - ion_CSP/gen_opt.py +378 -0
 - ion_CSP/identify_molecules.py +88 -0
 - ion_CSP/log_and_time.py +234 -0
 - ion_CSP/mlp_opt.py +154 -0
 - ion_CSP/read_mlp_density.py +144 -0
 - ion_CSP/steps_opt_monitor.sh +110 -0
 - ion_CSP/upload_download.py +487 -0
 - ion_CSP/vasp_processing.py +299 -0
 - ion_csp-2.0.2.dist-info/METADATA +83 -0
 - ion_csp-2.0.2.dist-info/RECORD +18 -0
 - ion_csp-2.0.2.dist-info/WHEEL +5 -0
 - ion_csp-2.0.2.dist-info/entry_points.txt +2 -0
 - ion_csp-2.0.2.dist-info/licenses/LICENSE +21 -0
 - ion_csp-2.0.2.dist-info/top_level.txt +1 -0
 
    
        ion_CSP/__init__.py
    ADDED
    
    
    
        ion_CSP/app.py
    ADDED
    
    | 
         @@ -0,0 +1,201 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env python3
         
     | 
| 
      
 2 
     | 
    
         
            +
            import os
         
     | 
| 
      
 3 
     | 
    
         
            +
            import sys
         
     | 
| 
      
 4 
     | 
    
         
            +
            import time
         
     | 
| 
      
 5 
     | 
    
         
            +
            import signal
         
     | 
| 
      
 6 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 7 
     | 
    
         
            +
            import subprocess
         
     | 
| 
      
 8 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 9 
     | 
    
         
            +
            from datetime import datetime
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            class TaskManager:
         
     | 
| 
      
 13 
     | 
    
         
            +
                def __init__(self):
         
     | 
| 
      
 14 
     | 
    
         
            +
                    self.env = "LOCAL"
         
     | 
| 
      
 15 
     | 
    
         
            +
                    self.workspace = Path.cwd()
         
     | 
| 
      
 16 
     | 
    
         
            +
                    self.log_base = self.workspace / "logs"
         
     | 
| 
      
 17 
     | 
    
         
            +
                    self._detect_env()
         
     | 
| 
      
 18 
     | 
    
         
            +
                    self._setup_logging()
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def _detect_env(self):
         
     | 
| 
      
 21 
     | 
    
         
            +
                    """环境检测"""
         
     | 
| 
      
 22 
     | 
    
         
            +
                    if Path("/.dockerenv").exists() or "DOCKER" in os.environ:
         
     | 
| 
      
 23 
     | 
    
         
            +
                        self.env = "DOCKER"
         
     | 
| 
      
 24 
     | 
    
         
            +
                        self.workspace = Path("/app")
         
     | 
| 
      
 25 
     | 
    
         
            +
                        self.log_base = Path("/app/logs")
         
     | 
| 
      
 26 
     | 
    
         
            +
                    self.workspace.mkdir(exist_ok=True)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    self.log_base.mkdir(exist_ok=True)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def _setup_logging(self):
         
     | 
| 
      
 30 
     | 
    
         
            +
                    """日志配置"""
         
     | 
| 
      
 31 
     | 
    
         
            +
                    logging.basicConfig(
         
     | 
| 
      
 32 
     | 
    
         
            +
                        level=logging.INFO,
         
     | 
| 
      
 33 
     | 
    
         
            +
                        format="%(asctime)s - %(levelname)s - %(message)s",
         
     | 
| 
      
 34 
     | 
    
         
            +
                        handlers=[
         
     | 
| 
      
 35 
     | 
    
         
            +
                            logging.FileHandler(self.log_base / "system.log"),
         
     | 
| 
      
 36 
     | 
    
         
            +
                            logging.StreamHandler(),
         
     | 
| 
      
 37 
     | 
    
         
            +
                        ],
         
     | 
| 
      
 38 
     | 
    
         
            +
                    )
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def normalize_path(self, path):
         
     | 
| 
      
 41 
     | 
    
         
            +
                    """路径标准化"""
         
     | 
| 
      
 42 
     | 
    
         
            +
                    path = Path(path).resolve()
         
     | 
| 
      
 43 
     | 
    
         
            +
                    if self.env == "DOCKER":
         
     | 
| 
      
 44 
     | 
    
         
            +
                        return str(path.relative_to(self.workspace))
         
     | 
| 
      
 45 
     | 
    
         
            +
                    return str(path)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def _get_pid(self, module, work_dir):
         
     | 
| 
      
 48 
     | 
    
         
            +
                    """获取进程PID"""
         
     | 
| 
      
 49 
     | 
    
         
            +
                    log_file = Path(work_dir) / f"main_{module}_console.log"
         
     | 
| 
      
 50 
     | 
    
         
            +
                    if not log_file.exists():
         
     | 
| 
      
 51 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 52 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 53 
     | 
    
         
            +
                        with open(log_file, "r") as f:
         
     | 
| 
      
 54 
     | 
    
         
            +
                            for line in f:
         
     | 
| 
      
 55 
     | 
    
         
            +
                                if "PYTHON_PID:" in line:
         
     | 
| 
      
 56 
     | 
    
         
            +
                                    return int(line.split(":")[-1].strip())
         
     | 
| 
      
 57 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 58 
     | 
    
         
            +
                        logging.error(f"Error reading PID from log: {e}")
         
     | 
| 
      
 59 
     | 
    
         
            +
                    return None
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                def task_runner(self, module, work_dir):
         
     | 
| 
      
 62 
     | 
    
         
            +
                    """任务执行器"""
         
     | 
| 
      
 63 
     | 
    
         
            +
                    work_dir = Path(work_dir)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    work_dir.mkdir(exist_ok=True)
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    console_log = work_dir / f"main_{module}_console.log"
         
     | 
| 
      
 67 
     | 
    
         
            +
                    pid_file = work_dir / "pid.txt"
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                    # 启动子进程
         
     | 
| 
      
 70 
     | 
    
         
            +
                    cmd = ["python", "-m", f"src.main_{module}", str(work_dir)]
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    with open(console_log, "w") as f:
         
     | 
| 
      
 73 
     | 
    
         
            +
                        process = subprocess.Popen(
         
     | 
| 
      
 74 
     | 
    
         
            +
                            cmd,
         
     | 
| 
      
 75 
     | 
    
         
            +
                            stdout=f,
         
     | 
| 
      
 76 
     | 
    
         
            +
                            stderr=subprocess.STDOUT,
         
     | 
| 
      
 77 
     | 
    
         
            +
                            preexec_fn=os.setsid if os.name != "nt" else None,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        )
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    # 等待PID文件创建
         
     | 
| 
      
 81 
     | 
    
         
            +
                    time.sleep(1)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 83 
     | 
    
         
            +
                        with open(pid_file, "w") as f:
         
     | 
| 
      
 84 
     | 
    
         
            +
                            f.write(str(process.pid))
         
     | 
| 
      
 85 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 86 
     | 
    
         
            +
                        logging.error(f"Error writing PID file: {e}")
         
     | 
| 
      
 87 
     | 
    
         
            +
                        process.terminate()
         
     | 
| 
      
 88 
     | 
    
         
            +
                        return
         
     | 
| 
      
 89 
     | 
    
         
            +
                    # 创建符号链接
         
     | 
| 
      
 90 
     | 
    
         
            +
                    output_log = work_dir / f"main_{module}.py_output.log"
         
     | 
| 
      
 91 
     | 
    
         
            +
                    print(f"Original log file: {output_log}")
         
     | 
| 
      
 92 
     | 
    
         
            +
                    std_log = self.log_base / f"{module}_{process.pid}.log"
         
     | 
| 
      
 93 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 94 
     | 
    
         
            +
                        std_log.symlink_to(output_log)
         
     | 
| 
      
 95 
     | 
    
         
            +
                        os.remove(pid_file)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    except FileExistsError:
         
     | 
| 
      
 97 
     | 
    
         
            +
                        os.remove(std_log)
         
     | 
| 
      
 98 
     | 
    
         
            +
                        std_log.symlink_to(output_log)
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    logging.info(f"Started {module} module (PID: {process.pid})")
         
     | 
| 
      
 101 
     | 
    
         
            +
                    print(f"Task started (PID: {process.pid})")
         
     | 
| 
      
 102 
     | 
    
         
            +
                    print(f"Normalized log file: {std_log}")
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def terminate_task(self, pid):
         
     | 
| 
      
 105 
     | 
    
         
            +
                    """终止任务"""
         
     | 
| 
      
 106 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 107 
     | 
    
         
            +
                        os.killpg(os.getpgid(pid), signal.SIGTERM)
         
     | 
| 
      
 108 
     | 
    
         
            +
                        print(f"Successfully terminated PID {pid}")
         
     | 
| 
      
 109 
     | 
    
         
            +
                    except ProcessLookupError:
         
     | 
| 
      
 110 
     | 
    
         
            +
                        print(f"No process found with PID {pid}")
         
     | 
| 
      
 111 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 112 
     | 
    
         
            +
                        print(f"Error terminating process: {e}")
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                def view_logs(self, page_size=10):
         
     | 
| 
      
 115 
     | 
    
         
            +
                    """查看日志"""
         
     | 
| 
      
 116 
     | 
    
         
            +
                    log_files = sorted(
         
     | 
| 
      
 117 
     | 
    
         
            +
                        self.log_base.glob("**/*.log"), key=os.path.getmtime, reverse=True
         
     | 
| 
      
 118 
     | 
    
         
            +
                    )
         
     | 
| 
      
 119 
     | 
    
         
            +
                    if not log_files:
         
     | 
| 
      
 120 
     | 
    
         
            +
                        print("No logs found")
         
     | 
| 
      
 121 
     | 
    
         
            +
                        return
         
     | 
| 
      
 122 
     | 
    
         
            +
                    total_files = len(log_files)
         
     | 
| 
      
 123 
     | 
    
         
            +
                    total_pages = (total_files + page_size - 1) // page_size  # 计算总页数
         
     | 
| 
      
 124 
     | 
    
         
            +
                    
         
     | 
| 
      
 125 
     | 
    
         
            +
                    current_page = 0
         
     | 
| 
      
 126 
     | 
    
         
            +
                    while True:
         
     | 
| 
      
 127 
     | 
    
         
            +
                        start_index = current_page * page_size
         
     | 
| 
      
 128 
     | 
    
         
            +
                        end_index = start_index + page_size
         
     | 
| 
      
 129 
     | 
    
         
            +
                        print("\nAvailable logs:")
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                        # 显示当前页的日志文件
         
     | 
| 
      
 132 
     | 
    
         
            +
                        for i, f in enumerate(log_files[start_index:end_index], start_index + 1):
         
     | 
| 
      
 133 
     | 
    
         
            +
                            print(
         
     | 
| 
      
 134 
     | 
    
         
            +
                                f"{i}) {f.name} ({datetime.fromtimestamp(f.stat().st_mtime).strftime('%Y-%m-%d %H:%M')})"
         
     | 
| 
      
 135 
     | 
    
         
            +
                            )
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
                        print("\nPage {} of {}".format(current_page + 1, total_pages))
         
     | 
| 
      
 138 
     | 
    
         
            +
                        if current_page > 0:
         
     | 
| 
      
 139 
     | 
    
         
            +
                            print("Enter 'p' to go to the previous page.")
         
     | 
| 
      
 140 
     | 
    
         
            +
                        if current_page < total_pages - 1:
         
     | 
| 
      
 141 
     | 
    
         
            +
                            print("Enter 'n' to go to the next page.")
         
     | 
| 
      
 142 
     | 
    
         
            +
                        print("Enter log number to view (q to cancel): ")
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                        choice = input().strip()
         
     | 
| 
      
 145 
     | 
    
         
            +
                        if choice.isdigit():
         
     | 
| 
      
 146 
     | 
    
         
            +
                            choice_index = int(choice) - 1
         
     | 
| 
      
 147 
     | 
    
         
            +
                            if 0 <= choice_index < total_files:
         
     | 
| 
      
 148 
     | 
    
         
            +
                                os.system(f"less {log_files[choice_index]}")
         
     | 
| 
      
 149 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 150 
     | 
    
         
            +
                                print("Invalid selection")
         
     | 
| 
      
 151 
     | 
    
         
            +
                        elif choice == "n" and current_page < total_pages - 1:
         
     | 
| 
      
 152 
     | 
    
         
            +
                            current_page += 1
         
     | 
| 
      
 153 
     | 
    
         
            +
                        elif choice == "p" and current_page > 0:
         
     | 
| 
      
 154 
     | 
    
         
            +
                            current_page -= 1
         
     | 
| 
      
 155 
     | 
    
         
            +
                        elif choice == "q":
         
     | 
| 
      
 156 
     | 
    
         
            +
                            break
         
     | 
| 
      
 157 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 158 
     | 
    
         
            +
                            print("Invalid command")
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                def main_menu(self):
         
     | 
| 
      
 161 
     | 
    
         
            +
                    """主菜单循环"""
         
     | 
| 
      
 162 
     | 
    
         
            +
                    while True:
         
     | 
| 
      
 163 
     | 
    
         
            +
                        os.system("clear" if os.name == "posix" else "cls")
         
     | 
| 
      
 164 
     | 
    
         
            +
                        print("========== Task Execution System ==========")
         
     | 
| 
      
 165 
     | 
    
         
            +
                        print(f"Current Environment: {self.env}")
         
     | 
| 
      
 166 
     | 
    
         
            +
                        print(f"Current Directory: {self.workspace}")
         
     | 
| 
      
 167 
     | 
    
         
            +
                        print(f"Log Base Directory: {self.log_base}")
         
     | 
| 
      
 168 
     | 
    
         
            +
                        print("=" * 50)
         
     | 
| 
      
 169 
     | 
    
         
            +
                        print("1) Run EE Module")
         
     | 
| 
      
 170 
     | 
    
         
            +
                        print("2) Run CSP Module")
         
     | 
| 
      
 171 
     | 
    
         
            +
                        print("3) View Logs")
         
     | 
| 
      
 172 
     | 
    
         
            +
                        print("4) Terminate Task")
         
     | 
| 
      
 173 
     | 
    
         
            +
                        print("q) Exit")
         
     | 
| 
      
 174 
     | 
    
         
            +
                        print("=" * 50)
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                        choice = input("Please select one of the operation: ").strip()
         
     | 
| 
      
 177 
     | 
    
         
            +
                        if choice == "1":
         
     | 
| 
      
 178 
     | 
    
         
            +
                            work_dir = input("Enter EE working directory: ").strip()
         
     | 
| 
      
 179 
     | 
    
         
            +
                            self.task_runner("EE", work_dir)
         
     | 
| 
      
 180 
     | 
    
         
            +
                        elif choice == "2":
         
     | 
| 
      
 181 
     | 
    
         
            +
                            work_dir = input("Enter CSP working directory: ").strip()
         
     | 
| 
      
 182 
     | 
    
         
            +
                            self.task_runner("CSP", work_dir)
         
     | 
| 
      
 183 
     | 
    
         
            +
                        elif choice == "3":
         
     | 
| 
      
 184 
     | 
    
         
            +
                            self.view_logs()
         
     | 
| 
      
 185 
     | 
    
         
            +
                        elif choice == "4":
         
     | 
| 
      
 186 
     | 
    
         
            +
                            pid = input("Enter PID to terminate: ").strip()
         
     | 
| 
      
 187 
     | 
    
         
            +
                            if pid.isdigit():
         
     | 
| 
      
 188 
     | 
    
         
            +
                                self.terminate_task(int(pid))
         
     | 
| 
      
 189 
     | 
    
         
            +
                            else:
         
     | 
| 
      
 190 
     | 
    
         
            +
                                print("Invalid PID format")
         
     | 
| 
      
 191 
     | 
    
         
            +
                        elif choice == "q":
         
     | 
| 
      
 192 
     | 
    
         
            +
                            print("Exiting system...")
         
     | 
| 
      
 193 
     | 
    
         
            +
                            sys.exit(0)
         
     | 
| 
      
 194 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 195 
     | 
    
         
            +
                            print("Invalid selection")
         
     | 
| 
      
 196 
     | 
    
         
            +
                        input("\nPress Enter to continue...")
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
            if __name__ == "__main__":
         
     | 
| 
      
 200 
     | 
    
         
            +
                manager = TaskManager()
         
     | 
| 
      
 201 
     | 
    
         
            +
                manager.main_menu()
         
     | 
| 
         @@ -0,0 +1,291 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import os
         
     | 
| 
      
 2 
     | 
    
         
            +
            import shutil
         
     | 
| 
      
 3 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 4 
     | 
    
         
            +
            import pandas as pd
         
     | 
| 
      
 5 
     | 
    
         
            +
            from typing import List
         
     | 
| 
      
 6 
     | 
    
         
            +
            from rdkit import Chem
         
     | 
| 
      
 7 
     | 
    
         
            +
            from rdkit.Chem import AllChem
         
     | 
| 
      
 8 
     | 
    
         
            +
            from dpdispatcher import Machine, Resources, Task, Submission
         
     | 
| 
      
 9 
     | 
    
         
            +
            from ion_CSP.log_and_time import redirect_dpdisp_logging
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            class SmilesProcessing:
         
     | 
| 
      
 13 
     | 
    
         
            +
                
         
     | 
| 
      
 14 
     | 
    
         
            +
                def __init__(self, work_dir: str, csv_file: str, converted_folder: str = '1_1_SMILES_gjf', optimized_dir: str = '1_2_Gaussian_optimized'):
         
     | 
| 
      
 15 
     | 
    
         
            +
                    """
         
     | 
| 
      
 16 
     | 
    
         
            +
                    args:
         
     | 
| 
      
 17 
     | 
    
         
            +
                        work_dir: the path of the working directory.
         
     | 
| 
      
 18 
     | 
    
         
            +
                        csv_file: the csv file name in the working directory.
         
     | 
| 
      
 19 
     | 
    
         
            +
                    """
         
     | 
| 
      
 20 
     | 
    
         
            +
                    redirect_dpdisp_logging(os.path.join(work_dir, "dpdispatcher.log"))
         
     | 
| 
      
 21 
     | 
    
         
            +
                    # 读取csv文件并处理数据, csv文件的表头包括 SMILES, Charge, Refcode或Number
         
     | 
| 
      
 22 
     | 
    
         
            +
                    self.base_dir = work_dir
         
     | 
| 
      
 23 
     | 
    
         
            +
                    os.chdir(work_dir)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    if not csv_file:
         
     | 
| 
      
 25 
     | 
    
         
            +
                        raise Exception('Necessary .csv file not provided!')
         
     | 
| 
      
 26 
     | 
    
         
            +
                    csv_path = os.path.join(self.base_dir, csv_file)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    self.converted_dir = os.path.join(
         
     | 
| 
      
 28 
     | 
    
         
            +
                        self.base_dir, converted_folder, os.path.splitext(csv_file)[0]
         
     | 
| 
      
 29 
     | 
    
         
            +
                    )
         
     | 
| 
      
 30 
     | 
    
         
            +
                    self.gaussian_optimized_dir = os.path.join(self.base_dir, optimized_dir)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    self.param_dir = os.path.join(os.path.dirname(__file__), "../../param")
         
     | 
| 
      
 32 
     | 
    
         
            +
                    original_df = pd.read_csv(csv_path)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    logging.info(f"Processing {csv_path}")
         
     | 
| 
      
 34 
     | 
    
         
            +
                    # 对SMILES码去重
         
     | 
| 
      
 35 
     | 
    
         
            +
                    df = original_df.drop_duplicates(subset="SMILES")
         
     | 
| 
      
 36 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 37 
     | 
    
         
            +
                        # 根据Refcode进行排序
         
     | 
| 
      
 38 
     | 
    
         
            +
                        df = df.sort_values(by="Refcode")
         
     | 
| 
      
 39 
     | 
    
         
            +
                        self.base_name = "Refcode"
         
     | 
| 
      
 40 
     | 
    
         
            +
                    except KeyError:
         
     | 
| 
      
 41 
     | 
    
         
            +
                        # 如果不存在Refcode,则根据Number进行排序
         
     | 
| 
      
 42 
     | 
    
         
            +
                        df = df.sort_values(by="Number")
         
     | 
| 
      
 43 
     | 
    
         
            +
                        self.base_name = "Number"
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # 根据Charge分组
         
     | 
| 
      
 45 
     | 
    
         
            +
                    grouped = df.groupby("Charge")
         
     | 
| 
      
 46 
     | 
    
         
            +
                    duplicate_message = f"\nOriginal SMILES dataset: {len(original_df)}\nAfter SMILES deduplication\n Valid SMILES: {len(df)}\n Duplicate SMILES: {len(original_df) - len(df)}"
         
     | 
| 
      
 47 
     | 
    
         
            +
                    logging.info(duplicate_message)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    self.csv = csv_file.split(".csv")[0]
         
     | 
| 
      
 49 
     | 
    
         
            +
                    self.df = df
         
     | 
| 
      
 50 
     | 
    
         
            +
                    self.grouped = grouped
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def _convert_SMILES(
         
     | 
| 
      
 53 
     | 
    
         
            +
                    self, dir: str, smiles: str, basename: str, charge: int
         
     | 
| 
      
 54 
     | 
    
         
            +
                ):
         
     | 
| 
      
 55 
     | 
    
         
            +
                    """
         
     | 
| 
      
 56 
     | 
    
         
            +
                    Private method: Use the rdkit module to read SMILES code and convert it into the required file types such as gjf, xyz, mol, etc.
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    args:
         
     | 
| 
      
 59 
     | 
    
         
            +
                        dir: The directory used for outputting files, regardless of existence of the directory.
         
     | 
| 
      
 60 
     | 
    
         
            +
                        smiles: SMILES code to be converted.
         
     | 
| 
      
 61 
     | 
    
         
            +
                        basename: The reference code or number corresponding to SMILES code.
         
     | 
| 
      
 62 
     | 
    
         
            +
                        charge: The charge carried by ions.
         
     | 
| 
      
 63 
     | 
    
         
            +
                    return:
         
     | 
| 
      
 64 
     | 
    
         
            +
                        result_code: Result code 0 or -1, representing success and failure respectively.
         
     | 
| 
      
 65 
     | 
    
         
            +
                        basename: The corresponding basename.
         
     | 
| 
      
 66 
     | 
    
         
            +
                    """
         
     | 
| 
      
 67 
     | 
    
         
            +
                    mol = Chem.MolFromSmiles(smiles)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    mol = Chem.AddHs(mol)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 70 
     | 
    
         
            +
                        # 生成3D坐标
         
     | 
| 
      
 71 
     | 
    
         
            +
                        AllChem.EmbedMolecule(mol)
         
     | 
| 
      
 72 
     | 
    
         
            +
                        AllChem.UFFOptimizeMolecule(mol)
         
     | 
| 
      
 73 
     | 
    
         
            +
                        # 获取原子信息
         
     | 
| 
      
 74 
     | 
    
         
            +
                        conf = mol.GetConformer()
         
     | 
| 
      
 75 
     | 
    
         
            +
                        num_atoms = mol.GetNumAtoms()
         
     | 
| 
      
 76 
     | 
    
         
            +
                        # 计算电荷与分子多重度
         
     | 
| 
      
 77 
     | 
    
         
            +
                        num_charge, num_unpaired_electrons = 0, 0
         
     | 
| 
      
 78 
     | 
    
         
            +
                        for atom in mol.GetAtoms():
         
     | 
| 
      
 79 
     | 
    
         
            +
                            num_charge += atom.GetFormalCharge()
         
     | 
| 
      
 80 
     | 
    
         
            +
                            num_unpaired_electrons += atom.GetNumRadicalElectrons()
         
     | 
| 
      
 81 
     | 
    
         
            +
                        if num_charge != charge:
         
     | 
| 
      
 82 
     | 
    
         
            +
                            logging.error(
         
     | 
| 
      
 83 
     | 
    
         
            +
                                f"{basename}: charge wrong! calculated {num_charge} and given {charge}"
         
     | 
| 
      
 84 
     | 
    
         
            +
                            )
         
     | 
| 
      
 85 
     | 
    
         
            +
                        multiplicity = 2 * num_unpaired_electrons + 1
         
     | 
| 
      
 86 
     | 
    
         
            +
                        # 根据type参数判断要生成什么类型的结构文件, 目前只支持gjf, xyz, mol格式
         
     | 
| 
      
 87 
     | 
    
         
            +
                        filename = f"{dir}/{basename}.gjf"    
         
     | 
| 
      
 88 
     | 
    
         
            +
                        # 创建gjf文件内容
         
     | 
| 
      
 89 
     | 
    
         
            +
                        gjf_content = f"%nprocshared=8\n%chk={basename}.chk\n#p B3LYP/6-31G** opt\n\n{basename}\n\n{num_charge} {multiplicity}\n"
         
     | 
| 
      
 90 
     | 
    
         
            +
                        for atom in range(num_atoms):
         
     | 
| 
      
 91 
     | 
    
         
            +
                            pos = conf.GetAtomPosition(atom)
         
     | 
| 
      
 92 
     | 
    
         
            +
                            atom_symbol = mol.GetAtomWithIdx(atom).GetSymbol()
         
     | 
| 
      
 93 
     | 
    
         
            +
                            gjf_content += (
         
     | 
| 
      
 94 
     | 
    
         
            +
                                f"{atom_symbol} {pos.x:.6f} {pos.y:.6f} {pos.z:.6f}\n"
         
     | 
| 
      
 95 
     | 
    
         
            +
                            )
         
     | 
| 
      
 96 
     | 
    
         
            +
                        # 写入gjf文件
         
     | 
| 
      
 97 
     | 
    
         
            +
                        with open(filename, "w") as gjf_file:
         
     | 
| 
      
 98 
     | 
    
         
            +
                            # gjf文件末尾需要空行,否则Gaussian会报End of file in ZSymb错误(l101.exe)
         
     | 
| 
      
 99 
     | 
    
         
            +
                            gjf_file.write(f"{gjf_content}\n\n")
         
     | 
| 
      
 100 
     | 
    
         
            +
                        result_code = 0
         
     | 
| 
      
 101 
     | 
    
         
            +
                    except Exception as e:  # 捕获运行过程中的错误
         
     | 
| 
      
 102 
     | 
    
         
            +
                        logging.error(
         
     | 
| 
      
 103 
     | 
    
         
            +
                            f"Error occurred while optimizing molecule of {basename} with charge {charge}: {e}"
         
     | 
| 
      
 104 
     | 
    
         
            +
                        )
         
     | 
| 
      
 105 
     | 
    
         
            +
                        result_code = 1
         
     | 
| 
      
 106 
     | 
    
         
            +
                    # 第一项返回值为结果码0或1, 分别代表成功和失败; 第二项返回值为对应的refcode或序号
         
     | 
| 
      
 107 
     | 
    
         
            +
                    return result_code, basename
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                def charge_group(self):
         
     | 
| 
      
 110 
     | 
    
         
            +
                    """
         
     | 
| 
      
 111 
     | 
    
         
            +
                    Create folders by grouping according to charges and convert SMILES codes into corresponding structural files.
         
     | 
| 
      
 112 
     | 
    
         
            +
                    """
         
     | 
| 
      
 113 
     | 
    
         
            +
                    # 分别记录生成结构成功和失败的refcode或序号
         
     | 
| 
      
 114 
     | 
    
         
            +
                    success, fail = [], []
         
     | 
| 
      
 115 
     | 
    
         
            +
                    for charge, group in self.grouped:
         
     | 
| 
      
 116 
     | 
    
         
            +
                        # 根据文件类型与电荷分组创建对应的文件夹
         
     | 
| 
      
 117 
     | 
    
         
            +
                        charge_dir = (
         
     | 
| 
      
 118 
     | 
    
         
            +
                            f"{self.converted_dir}/charge_{charge}"
         
     | 
| 
      
 119 
     | 
    
         
            +
                        )
         
     | 
| 
      
 120 
     | 
    
         
            +
                        os.makedirs(charge_dir, exist_ok=True)
         
     | 
| 
      
 121 
     | 
    
         
            +
                        # 通过SMILE_to函数依次处理SMILES码
         
     | 
| 
      
 122 
     | 
    
         
            +
                        for _, row in group.iterrows():
         
     | 
| 
      
 123 
     | 
    
         
            +
                            result_code, basename = self._convert_SMILES(
         
     | 
| 
      
 124 
     | 
    
         
            +
                                dir=charge_dir,
         
     | 
| 
      
 125 
     | 
    
         
            +
                                smiles=row["SMILES"],
         
     | 
| 
      
 126 
     | 
    
         
            +
                                basename=row[self.base_name],
         
     | 
| 
      
 127 
     | 
    
         
            +
                                charge=row["Charge"]
         
     | 
| 
      
 128 
     | 
    
         
            +
                            )
         
     | 
| 
      
 129 
     | 
    
         
            +
                            # 根据私有方法_convert_SMILES的返回值记录refcode对应的分子是否能够成功生成结构文件
         
     | 
| 
      
 130 
     | 
    
         
            +
                            if result_code == 0:
         
     | 
| 
      
 131 
     | 
    
         
            +
                                success.append(basename)
         
     | 
| 
      
 132 
     | 
    
         
            +
                            elif result_code == 1:
         
     | 
| 
      
 133 
     | 
    
         
            +
                                fail.append(basename)
         
     | 
| 
      
 134 
     | 
    
         
            +
                    # 将统计信息输出并记录到log文件中
         
     | 
| 
      
 135 
     | 
    
         
            +
                    generation_message = f"\nDuring the .gjf file generation process\n Successfully generated .gjf files: {len(success)}\n Errors encounted: {len(fail)}\n Error {self.base_name}: {fail}"
         
     | 
| 
      
 136 
     | 
    
         
            +
                    logging.info(generation_message)
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                def screen(
         
     | 
| 
      
 139 
     | 
    
         
            +
                    self,
         
     | 
| 
      
 140 
     | 
    
         
            +
                    charge_screen: int = 0,
         
     | 
| 
      
 141 
     | 
    
         
            +
                    group_screen: str = "",
         
     | 
| 
      
 142 
     | 
    
         
            +
                    group_name: str = "",
         
     | 
| 
      
 143 
     | 
    
         
            +
                    group_screen_invert: bool = False,
         
     | 
| 
      
 144 
     | 
    
         
            +
                ):
         
     | 
| 
      
 145 
     | 
    
         
            +
                    """
         
     | 
| 
      
 146 
     | 
    
         
            +
                    Screen based on the provided functional groups and charges.
         
     | 
| 
      
 147 
     | 
    
         
            +
                    """
         
     | 
| 
      
 148 
     | 
    
         
            +
                    # 另外筛选出符合条件的离子
         
     | 
| 
      
 149 
     | 
    
         
            +
                    screened = self.df
         
     | 
| 
      
 150 
     | 
    
         
            +
                    if group_screen:
         
     | 
| 
      
 151 
     | 
    
         
            +
                        if group_screen_invert:
         
     | 
| 
      
 152 
     | 
    
         
            +
                            screened = screened[
         
     | 
| 
      
 153 
     | 
    
         
            +
                                ~screened["SMILES"].str.contains(group_screen, regex=False)
         
     | 
| 
      
 154 
     | 
    
         
            +
                            ]
         
     | 
| 
      
 155 
     | 
    
         
            +
                        else:
         
     | 
| 
      
 156 
     | 
    
         
            +
                            screened = screened[
         
     | 
| 
      
 157 
     | 
    
         
            +
                                screened["SMILES"].str.contains(group_screen, regex=False)
         
     | 
| 
      
 158 
     | 
    
         
            +
                            ]
         
     | 
| 
      
 159 
     | 
    
         
            +
                    if charge_screen:
         
     | 
| 
      
 160 
     | 
    
         
            +
                        screened = screened[screened["Charge"] == charge_screen]
         
     | 
| 
      
 161 
     | 
    
         
            +
                    screened_message = f"\nNumber of ions with charge of [{charge_screen}] and {group_name} group: {len(screened)}\n"
         
     | 
| 
      
 162 
     | 
    
         
            +
                    logging.info(screened_message)
         
     | 
| 
      
 163 
     | 
    
         
            +
                    # 另外创建文件夹, 并依次处理SMILES码
         
     | 
| 
      
 164 
     | 
    
         
            +
                    screened_dir = f"{self.converted_dir}/{group_name}_{charge_screen}"
         
     | 
| 
      
 165 
     | 
    
         
            +
                    os.makedirs(screened_dir, exist_ok=True)
         
     | 
| 
      
 166 
     | 
    
         
            +
                    for _, row in screened.iterrows():
         
     | 
| 
      
 167 
     | 
    
         
            +
                        self._convert_SMILES(
         
     | 
| 
      
 168 
     | 
    
         
            +
                            dir=screened_dir,
         
     | 
| 
      
 169 
     | 
    
         
            +
                            smiles=row["SMILES"],
         
     | 
| 
      
 170 
     | 
    
         
            +
                            basename=row[self.base_name],
         
     | 
| 
      
 171 
     | 
    
         
            +
                            charge=row["Charge"]
         
     | 
| 
      
 172 
     | 
    
         
            +
                        )
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                def dpdisp_gaussian_tasks(self,
         
     | 
| 
      
 175 
     | 
    
         
            +
                    folders: List[str] = [],
         
     | 
| 
      
 176 
     | 
    
         
            +
                    machine: str = "",
         
     | 
| 
      
 177 
     | 
    
         
            +
                    resources: str = "",
         
     | 
| 
      
 178 
     | 
    
         
            +
                    nodes: int = 1,
         
     | 
| 
      
 179 
     | 
    
         
            +
                ):
         
     | 
| 
      
 180 
     | 
    
         
            +
                    """
         
     | 
| 
      
 181 
     | 
    
         
            +
                    Based on the dpdispatcher module, prepare and submit files for optimization on remote server or local machine.
         
     | 
| 
      
 182 
     | 
    
         
            +
                    """
         
     | 
| 
      
 183 
     | 
    
         
            +
                    if os.path.exists(self.gaussian_optimized_dir):
         
     | 
| 
      
 184 
     | 
    
         
            +
                        logging.error(f'The directory {self.gaussian_optimized_dir} has already existed.')
         
     | 
| 
      
 185 
     | 
    
         
            +
                        return
         
     | 
| 
      
 186 
     | 
    
         
            +
                    if not folders:
         
     | 
| 
      
 187 
     | 
    
         
            +
                        logging.error('No available folders for dpdispatcher to process Gaussian tasks.')
         
     | 
| 
      
 188 
     | 
    
         
            +
                        return
         
     | 
| 
      
 189 
     | 
    
         
            +
                    # 调整工作目录,减少错误发生
         
     | 
| 
      
 190 
     | 
    
         
            +
                    os.chdir(self.converted_dir)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    # 读取machine和resources的参数
         
     | 
| 
      
 192 
     | 
    
         
            +
                    if machine.endswith(".json"):
         
     | 
| 
      
 193 
     | 
    
         
            +
                        machine = Machine.load_from_json(machine)
         
     | 
| 
      
 194 
     | 
    
         
            +
                    elif machine.endswith(".yaml"):
         
     | 
| 
      
 195 
     | 
    
         
            +
                        machine = Machine.load_from_yaml(machine)
         
     | 
| 
      
 196 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 197 
     | 
    
         
            +
                        raise KeyError("Not supported machine file type")
         
     | 
| 
      
 198 
     | 
    
         
            +
                    if resources.endswith(".json"):
         
     | 
| 
      
 199 
     | 
    
         
            +
                        resources = Resources.load_from_json(resources)
         
     | 
| 
      
 200 
     | 
    
         
            +
                    elif resources.endswith(".yaml"):
         
     | 
| 
      
 201 
     | 
    
         
            +
                        resources = Resources.load_from_yaml(resources)
         
     | 
| 
      
 202 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 203 
     | 
    
         
            +
                        raise KeyError("Not supported resources file type")
         
     | 
| 
      
 204 
     | 
    
         
            +
                    # 由于dpdispatcher对于远程服务器以及本地运行的forward_common_files的默认存放位置不同,因此需要预先进行判断,从而不改动优化脚本
         
     | 
| 
      
 205 
     | 
    
         
            +
                    machine_inform = machine.serialize()
         
     | 
| 
      
 206 
     | 
    
         
            +
                    if machine_inform["context_type"] == "SSHContext":
         
     | 
| 
      
 207 
     | 
    
         
            +
                        # 如果调用远程服务器,则创建二级目录
         
     | 
| 
      
 208 
     | 
    
         
            +
                        parent = "data/"
         
     | 
| 
      
 209 
     | 
    
         
            +
                    elif machine_inform["context_type"] == "LocalContext":
         
     | 
| 
      
 210 
     | 
    
         
            +
                        # 如果在本地运行作业,则只在后续创建一级目录
         
     | 
| 
      
 211 
     | 
    
         
            +
                        parent = ""
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                    for folder in folders:
         
     | 
| 
      
 214 
     | 
    
         
            +
                        folder_dir = os.path.join(self.converted_dir, folder)
         
     | 
| 
      
 215 
     | 
    
         
            +
                        if not os.path.exists(folder_dir):
         
     | 
| 
      
 216 
     | 
    
         
            +
                            logging.error(f'Provided folder {folder} is not in the directory {folder_dir}')
         
     | 
| 
      
 217 
     | 
    
         
            +
                            continue
         
     | 
| 
      
 218 
     | 
    
         
            +
                        # 获取文件夹中所有以 .gjf 结尾的文件
         
     | 
| 
      
 219 
     | 
    
         
            +
                        gjf_files = [
         
     | 
| 
      
 220 
     | 
    
         
            +
                            f for f in os.listdir(folder_dir) if f.endswith(".gjf")
         
     | 
| 
      
 221 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 222 
     | 
    
         
            +
                        # 创建一个嵌套列表来存储每个节点的任务并将文件平均依次分配给每个节点
         
     | 
| 
      
 223 
     | 
    
         
            +
                        # 例如:对于10个结构文件任务分发给4个节点的情况,则4个节点领到的任务分别[0, 4, 8], [1, 5, 9], [2, 6], [3, 7]
         
     | 
| 
      
 224 
     | 
    
         
            +
                        node_jobs = [[] for _ in range(nodes)]
         
     | 
| 
      
 225 
     | 
    
         
            +
                        for index, file in enumerate(gjf_files):
         
     | 
| 
      
 226 
     | 
    
         
            +
                            node_index = index % nodes
         
     | 
| 
      
 227 
     | 
    
         
            +
                            node_jobs[node_index].append(index)
         
     | 
| 
      
 228 
     | 
    
         
            +
                        task_list = []
         
     | 
| 
      
 229 
     | 
    
         
            +
                        for pop in range(nodes):
         
     | 
| 
      
 230 
     | 
    
         
            +
                            forward_files = ["g16_sub.sh"]
         
     | 
| 
      
 231 
     | 
    
         
            +
                            backward_files = ["log", "err"]
         
     | 
| 
      
 232 
     | 
    
         
            +
                            # 将所有参数文件各复制一份到每个 task_dir 目录下
         
     | 
| 
      
 233 
     | 
    
         
            +
                            task_dir = os.path.join(self.converted_dir, f"{parent}pop{pop}")
         
     | 
| 
      
 234 
     | 
    
         
            +
                            os.makedirs(task_dir, exist_ok=True)
         
     | 
| 
      
 235 
     | 
    
         
            +
                            for file in forward_files:
         
     | 
| 
      
 236 
     | 
    
         
            +
                                shutil.copyfile(f"{self.param_dir}/{file}", f"{task_dir}/{file}")
         
     | 
| 
      
 237 
     | 
    
         
            +
                            for job_i in node_jobs[pop]:
         
     | 
| 
      
 238 
     | 
    
         
            +
                                # 将分配好的 .gjf 文件添加到对应的上传文件中
         
     | 
| 
      
 239 
     | 
    
         
            +
                                forward_files.append(gjf_files[job_i])
         
     | 
| 
      
 240 
     | 
    
         
            +
                                base_name, _ = os.path.splitext(gjf_files[job_i])
         
     | 
| 
      
 241 
     | 
    
         
            +
                                # 每个 .gjf 文件在优化后都取回对应的 .log、.fchk 输出文件
         
     | 
| 
      
 242 
     | 
    
         
            +
                                for ext in ['log', 'fchk']:
         
     | 
| 
      
 243 
     | 
    
         
            +
                                    backward_files.append(f'{base_name}.{ext}')
         
     | 
| 
      
 244 
     | 
    
         
            +
                                shutil.copyfile(
         
     | 
| 
      
 245 
     | 
    
         
            +
                                    f"{folder_dir}/{gjf_files[job_i]}",
         
     | 
| 
      
 246 
     | 
    
         
            +
                                    f"{task_dir}/{gjf_files[job_i]}",
         
     | 
| 
      
 247 
     | 
    
         
            +
                                )
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
                            remote_task_dir = f"{parent}pop{pop}"
         
     | 
| 
      
 250 
     | 
    
         
            +
                            command = "chmod +x g16_sub.sh && ./g16_sub.sh"
         
     | 
| 
      
 251 
     | 
    
         
            +
                            task = Task(
         
     | 
| 
      
 252 
     | 
    
         
            +
                                command=command,
         
     | 
| 
      
 253 
     | 
    
         
            +
                                task_work_path=remote_task_dir,
         
     | 
| 
      
 254 
     | 
    
         
            +
                                forward_files=forward_files,
         
     | 
| 
      
 255 
     | 
    
         
            +
                                backward_files=backward_files,
         
     | 
| 
      
 256 
     | 
    
         
            +
                            )
         
     | 
| 
      
 257 
     | 
    
         
            +
                            task_list.append(task)
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                        submission = Submission(
         
     | 
| 
      
 260 
     | 
    
         
            +
                            work_base=self.converted_dir,
         
     | 
| 
      
 261 
     | 
    
         
            +
                            machine=machine,
         
     | 
| 
      
 262 
     | 
    
         
            +
                            resources=resources,
         
     | 
| 
      
 263 
     | 
    
         
            +
                            task_list=task_list,
         
     | 
| 
      
 264 
     | 
    
         
            +
                        )
         
     | 
| 
      
 265 
     | 
    
         
            +
                        submission.run_submission()
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                        # 创建用于存放优化后文件的 gaussian_optimized 目录
         
     | 
| 
      
 268 
     | 
    
         
            +
                        optimized_folder_dir = os.path.join(self.gaussian_optimized_dir, folder)
         
     | 
| 
      
 269 
     | 
    
         
            +
                        os.makedirs(optimized_folder_dir, exist_ok=True)
         
     | 
| 
      
 270 
     | 
    
         
            +
                        for pop in range(nodes):
         
     | 
| 
      
 271 
     | 
    
         
            +
                            # 从传回目录下的 pop 文件夹中将结果文件取到 gaussian_optimized 目录
         
     | 
| 
      
 272 
     | 
    
         
            +
                            task_dir = os.path.join(self.converted_dir, f"{parent}pop{pop}")
         
     | 
| 
      
 273 
     | 
    
         
            +
                            # 按照给定的 .gjf 结构文件读取 .log、 文件并复制
         
     | 
| 
      
 274 
     | 
    
         
            +
                            for job_i in node_jobs[pop]:
         
     | 
| 
      
 275 
     | 
    
         
            +
                                base_name, _ = os.path.splitext(gjf_files[job_i])
         
     | 
| 
      
 276 
     | 
    
         
            +
                                # 在优化后都取回每个 .gjf 文件对应的 .log、.fchk 输出文件
         
     | 
| 
      
 277 
     | 
    
         
            +
                                for ext in ['gjf', 'log', 'fchk']:
         
     | 
| 
      
 278 
     | 
    
         
            +
                                    shutil.copyfile(
         
     | 
| 
      
 279 
     | 
    
         
            +
                                        f"{task_dir}/{base_name}.{ext}",
         
     | 
| 
      
 280 
     | 
    
         
            +
                                        f"{optimized_folder_dir}/{base_name}.{ext}"
         
     | 
| 
      
 281 
     | 
    
         
            +
                                    )
         
     | 
| 
      
 282 
     | 
    
         
            +
                            # 在成功完成Gaussian优化后,删除 1_1_SMILES_gjf/{csv}/{parent}/pop{n} 文件夹以节省空间
         
     | 
| 
      
 283 
     | 
    
         
            +
                            shutil.rmtree(task_dir)
         
     | 
| 
      
 284 
     | 
    
         
            +
                    shutil.copyfile(
         
     | 
| 
      
 285 
     | 
    
         
            +
                        os.path.join(self.base_dir, "config.yaml"),
         
     | 
| 
      
 286 
     | 
    
         
            +
                        os.path.join(optimized_folder_dir, "config.yaml"),
         
     | 
| 
      
 287 
     | 
    
         
            +
                    )
         
     | 
| 
      
 288 
     | 
    
         
            +
                    if machine_inform["context_type"] == "SSHContext":
         
     | 
| 
      
 289 
     | 
    
         
            +
                        # 如果调用远程服务器,则删除data级目录
         
     | 
| 
      
 290 
     | 
    
         
            +
                        shutil.rmtree(os.path.join(self.converted_dir, parent))
         
     | 
| 
      
 291 
     | 
    
         
            +
                    logging.info("Batch Gaussian optimization completed!!!")
         
     |