ion-CSP 2.0.2__py3-none-any.whl → 2.0.4__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/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .task_manager import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
5
+
ion_CSP/gen_opt.py CHANGED
@@ -238,7 +238,7 @@ class CrystalGenerator:
238
238
  # 如果在本地运行作业,则只在后续创建一级目录
239
239
  parent = ""
240
240
  # 如果是本地运行,则根据显存占用率阈值,等待可用的GPU
241
- selected_gpu = wait_for_gpu(memory_percent_threshold=30, wait_time=300)
241
+ selected_gpu = wait_for_gpu(memory_percent_threshold=40, wait_time=600)
242
242
  os.environ["CUDA_VISIBLE_DEVICES"] = str(selected_gpu)
243
243
 
244
244
  from dpdispatcher import Resources, Task, Submission
@@ -75,12 +75,12 @@ def molecules_information(molecules: List[Dict[str, int]], molecules_flag: bool,
75
75
  output = []
76
76
  for element in fixed_order:
77
77
  if element in molecule:
78
- output.append(f"{element} {molecule[element]}")
78
+ output.append(f"{element}{molecule[element]}")
79
79
  # 如果有其他元素,添加到输出中
80
80
  for element in molecule:
81
81
  if element not in fixed_order:
82
- output.append(f"{element} {molecule[element]}")
83
- formatted_output = ' '.join(output)
82
+ output.append(f"{element}{molecule[element]}")
83
+ formatted_output = ''.join(output)
84
84
  logging.info(f' Molecule {idx + 1} (Total Atoms: {total_atoms}, Count: {count}): {formatted_output}')
85
85
  if molecules_flag:
86
86
  logging.info('Molecular Comparison Successful\n')
@@ -0,0 +1,396 @@
1
+ import os
2
+ import re
3
+ import sys
4
+ import time
5
+ import psutil
6
+ import logging
7
+ import subprocess
8
+ import importlib.util
9
+ from pathlib import Path
10
+
11
+
12
+ class TaskManager:
13
+ """任务管理器类 - Manages task execution and monitoring"""
14
+
15
+ def __init__(self):
16
+ """初始化任务管理器 - Initialize task manager"""
17
+ self.env = "LOCAL"
18
+ self.project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
19
+ self.workspace = Path.cwd()
20
+ self.log_base = "logs"
21
+ self.log_dir = self.workspace / self.log_base
22
+ self.version = self._get_version()
23
+ self._detect_env()
24
+ self._setup_logging()
25
+
26
+ def __repr__(self):
27
+ return f"Taskmanager(version={self.version}, env={self.env}, project_root={self.project_root}, workspace={self.workspace}, log_base={self.log_base}, log_dir={self.log_dir})"
28
+
29
+ def _get_version(self):
30
+ """版本获取"""
31
+ try:
32
+ # 使用 importlib 动态加载
33
+ spec = importlib.util.spec_from_file_location(
34
+ "ion_CSP",
35
+ os.path.join(self.project_root, "src/__init__.py")
36
+ )
37
+ module = importlib.util.module_from_spec(spec)
38
+ spec.loader.exec_module(module)
39
+ return module.__version__
40
+ except Exception as e:
41
+ logging.error(f"Version detection failed: {e}")
42
+ return "unknown"
43
+
44
+ def _detect_env(self):
45
+ """检测运行环境 - Detect execution environment"""
46
+ if Path("/.dockerenv").exists() or "DOCKER" in os.environ:
47
+ self.env = "DOCKER"
48
+ self.workspace = Path("/app")
49
+ self.log_dir = Path("/app/logs")
50
+ self.workspace.mkdir(exist_ok=True)
51
+
52
+ def _setup_logging(self):
53
+ """配置日志系统 - Configure logging system"""
54
+ self.log_dir = self.workspace / self.log_base
55
+ self.log_dir.mkdir(parents=True, exist_ok=True)
56
+ logging.basicConfig(
57
+ level=logging.INFO,
58
+ format="%(asctime)s - %(levelname)s - %(message)s",
59
+ handlers=[
60
+ logging.FileHandler(self.log_dir / "system.log"),
61
+ logging.StreamHandler(),
62
+ ],
63
+ )
64
+
65
+ def _cleanup_task_files(self, module: str, pid: int):
66
+ """清理任务相关文件"""
67
+ log_file = self.log_dir / f"{module}_{pid}.log"
68
+ if log_file.exists():
69
+ log_file.unlink()
70
+ print(f"Cleaned up orphaned log: {log_file.name}")
71
+
72
+ def _safe_kill(self, module: str, pid: int):
73
+ """安全终止进程并清理残留资源 - Safely kill process and cleanup orphan resources"""
74
+ try:
75
+ proc = psutil.Process(pid)
76
+ # 先尝试优雅终止并等待进程推出
77
+ proc.terminate()
78
+ print(f"Termination signal sent to PID {pid}")
79
+
80
+ # 正确处理进程退出状态
81
+ try:
82
+ exit_code = proc.wait(timeout=5)
83
+ print(f"PID {pid} exited with code {exit_code}")
84
+ exit_code = 0 # 正常退出
85
+ except psutil.TimeoutExpired:
86
+ print(f"PID {pid} did not exit gracefully, forcing termination...")
87
+ proc.kill()
88
+ exit_code = -1 # 强制终止标记
89
+ # 清理残留文件
90
+ self._cleanup_task_files(module, pid)
91
+ input("\nPress Enter to continue...")
92
+ return exit_code
93
+
94
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
95
+ print(f"PID {pid} already terminated")
96
+ self._cleanup_task_files(module, pid)
97
+ input("\nPress Enter to continue...")
98
+ return -2 # 进程不存在标记
99
+ except Exception as e:
100
+ print(f"Error terminating process: {str(e)}")
101
+ input("\nPress Enter to continue...")
102
+ return -3 # 其他错误标记
103
+
104
+ def _is_pid_running(self, pid: int):
105
+ """检查进程是否仍在运行 - Check the process status according to PID"""
106
+ try:
107
+ proc = psutil.Process(pid)
108
+ return proc.status() in (psutil.STATUS_RUNNING, psutil.STATUS_SLEEPING)
109
+ except psutil.NoSuchProcess:
110
+ return False
111
+
112
+ def _is_valid_task_pid(self, pid: int):
113
+ """验证PID是否属于当前程序的任务进程 - Valid the task PID according to log file"""
114
+ try:
115
+ proc = psutil.Process(pid)
116
+ cmdline = " ".join(proc.cmdline())
117
+
118
+ # 检查模块标识和Python环境
119
+ return "python" in proc.name().lower() and (
120
+ "main_CSP" in cmdline or "main_EE" in cmdline
121
+ )
122
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
123
+ return False
124
+
125
+ def _display_tasks(self, tasks, total, current_page, total_pages, function):
126
+ """标准化任务显示 - Standardized tasks display"""
127
+ display = "logs" if function == "view" else "tasks"
128
+ print(f"\033cPage {current_page}/{total_pages} ({total} {display})")
129
+ if function == "kill":
130
+ for i, task in enumerate(tasks, 1):
131
+ print(
132
+ f"{i:3}) [{task['module']}] PID:{task['pid']:5} [{task['status']}] - {task['real_log']}"
133
+ )
134
+ elif function == "view":
135
+ for i, task in enumerate(tasks, 1):
136
+ print(
137
+ f"{i:3}) [{task['module']}] - {task['real_log']}"
138
+ )
139
+ else:
140
+ raise ValueError(f"Not supported function {function}. Available function: 'view' and 'kill' ")
141
+ print("\nPage {} of {}".format(current_page, total_pages))
142
+ # 分页控制
143
+ print("\nOptions:")
144
+ if function == "view":
145
+ print("n) Next page | p) Previous page | f) Filter | q) Quit")
146
+ elif function == "kill":
147
+ print("n) Next page | p) Previous page | f) Filter | k) Kill | q) Quit")
148
+ else:
149
+ raise ValueError("Unexpected function parameter")
150
+
151
+ def _paginate_tasks(self, tasks, function, page_size=10):
152
+ """通用分页函数 - Universal tasks pagination"""
153
+ total = len(tasks)
154
+ pages = (total + page_size - 1) // page_size
155
+ current_page = 0
156
+ filter_bool = False
157
+
158
+ while True:
159
+ start = current_page * page_size
160
+ end = start + page_size
161
+ page_tasks = tasks[start:end]
162
+ if not filter_bool:
163
+ # 显示当前页内容
164
+ self._display_tasks(page_tasks, total, current_page+1, pages, function)
165
+
166
+ choice = input().strip().upper()
167
+ if choice == 'N' and current_page < pages-1:
168
+ current_page += 1
169
+ elif choice == 'P' and current_page > 0:
170
+ current_page -= 1
171
+ elif function == "kill" and choice == "K":
172
+ try:
173
+ task_num = input("Enter task number to kill: ").strip()
174
+ if not task_num.isdigit():
175
+ raise ValueError
176
+ # 计算全局任务索引
177
+ global_index = current_page * 10 + (int(task_num) - 1)
178
+ if 0 <= global_index < len(tasks):
179
+ selected_index = global_index
180
+ confirm = input(
181
+ f"Confirm termination of {tasks[selected_index]['module']} PID {tasks[selected_index]['pid']}? (y/n): "
182
+ ).lower()
183
+ if confirm == "y":
184
+ self._safe_kill(
185
+ module=tasks[selected_index]["module"],
186
+ pid=tasks[selected_index]["pid"],
187
+ )
188
+ break
189
+ else:
190
+ print("Invalid task number")
191
+ input("\nPress Enter to continue...")
192
+ except ValueError:
193
+ print("Please enter a valid number")
194
+ input("\nPress Enter to continue...")
195
+ elif function == "view" and choice.isdigit():
196
+ # 计算全局任务索引
197
+ global_index = current_page * 10 + (int(choice) - 1)
198
+ if 0 <= global_index < len(tasks):
199
+ selected_index = global_index
200
+ os.system(f"less {tasks[selected_index]['real_log']}")
201
+ else:
202
+ print("Invalid selection")
203
+ input("\nPress Enter to continue...")
204
+ elif choice == 'Q':
205
+ break
206
+ elif choice == 'F':
207
+ filter_module = input("Please enter a valid module name (CSP or EE)\n")
208
+ if filter_module in ("CSP", "EE"):
209
+ self.view_filtered_tasks(filter_module, function)
210
+ break
211
+ else:
212
+ print("Invalid task number")
213
+ input("\nPress Enter to continue...")
214
+ else:
215
+ print("Invalid command")
216
+ input("\nPress Enter to continue...")
217
+
218
+ def task_runner(self, module: str, work_dir: str):
219
+ """任务执行器 - Task execution handler"""
220
+ work_dir = Path(work_dir)
221
+ work_dir.mkdir(exist_ok=True)
222
+
223
+ console_log = work_dir / f"main_{module}_console.log"
224
+ pid_file = work_dir / "pid.txt"
225
+
226
+ # 启动子进程
227
+ cmd = ["python", "-m", f"src.main_{module}", str(work_dir)]
228
+
229
+ with open(console_log, "w") as f:
230
+ process = subprocess.Popen(
231
+ cmd,
232
+ stdout=f,
233
+ stderr=subprocess.STDOUT,
234
+ preexec_fn=os.setsid if os.name != "nt" else None,
235
+ )
236
+
237
+ # 等待PID文件创建
238
+ time.sleep(1)
239
+ try:
240
+ with open(pid_file, "w") as f:
241
+ f.write(str(process.pid))
242
+ except Exception as e:
243
+ logging.error(f"Error writing PID file: {e}")
244
+ process.terminate()
245
+ return
246
+ # 创建符号链接
247
+ output_log = work_dir / f"main_{module}.py_output.log"
248
+ print(f"Original log file: {output_log}")
249
+ std_log = Path(self.log_dir) / f"{module}_{process.pid}.log"
250
+ try:
251
+ output_log = output_log.resolve()
252
+ std_log.symlink_to(output_log)
253
+ os.remove(pid_file)
254
+ except FileExistsError:
255
+ os.remove(std_log)
256
+ std_log.symlink_to(output_log)
257
+
258
+ logging.info(f"Started {module} module (PID: {process.pid})")
259
+ print(f"Task started (PID: {process.pid})")
260
+ print(f"Normalized log file: {std_log}")
261
+
262
+ def view_logs(self, page_size: int = 10):
263
+ """查看日志 - View task logs"""
264
+ log_tasks = []
265
+ log_pattern = re.compile(r"(CSP|EE)_\d+$") # 正则匹配规范文件名
266
+ for log_file in self.log_dir.glob("**/*.log"):
267
+ if not log_pattern.match(log_file.stem):
268
+ continue
269
+ try:
270
+ file_path = log_file.resolve(strict=True)
271
+ mtime = file_path.stat().st_mtime
272
+ log_tasks.append({
273
+ "pid": 0, # 日志无PID
274
+ "module": log_file.stem.split("_")[0].upper(),
275
+ "real_log": str(file_path),
276
+ "mtime": mtime,
277
+ "log_name": log_file.name,
278
+ "status": "Static"
279
+ })
280
+ except Exception as e:
281
+ logging.error(f"Error processing {log_file}: {e}")
282
+ continue
283
+ self._paginate_tasks(log_tasks, function="view", page_size=page_size)
284
+
285
+ def safe_terminate(self):
286
+ """安全终止任务 - Safe task termination"""
287
+ tasks = self.get_related_tasks()
288
+ if not tasks:
289
+ print("No running tasks found")
290
+ input("\nPress Enter to continue...")
291
+ return
292
+
293
+ # 分页显示任务列表
294
+ filter_bool = False
295
+ while True:
296
+ print("\033c", end="") # 清屏指令
297
+ # 显示当前页任务
298
+ print(f"\nRunning tasks ({len(tasks)} in total):")
299
+ if not filter_bool:
300
+ self._paginate_tasks(tasks, function="kill")
301
+ break
302
+
303
+ def view_filtered_tasks(self, module_filter: str, function: str):
304
+ """带分页的过滤任务显示"""
305
+ all_tasks = self.get_related_tasks()
306
+ filtered = [t for t in all_tasks if t["module"] == module_filter.upper()]
307
+ if not filtered:
308
+ print("No matching tasks found")
309
+ input("\nPress Enter to continue...")
310
+ return
311
+ print(f"\033cFiltered Tasks ({len(filtered)}):")
312
+ self._paginate_tasks(filtered, function) # 复用通用分页逻辑
313
+
314
+ def get_related_tasks(self):
315
+ """获取实时任务列表并验证状态 - Get relatd tasks list and validate the status"""
316
+ tasks = []
317
+ log_pattern = re.compile(r"(CSP|EE)_\d+$") # 正则匹配规范文件名
318
+
319
+ for log_file in self.log_dir.glob("**/*.log"):
320
+ if not log_pattern.match(log_file.stem):
321
+ continue # 跳过非标准日志文件
322
+ try:
323
+ # 使用正则提取模块和PID
324
+ match = re.match(r"(CSP|EE)_(\d+)$", log_file.stem)
325
+ module = match.group(1).upper()
326
+ pid = int(match.group(2))
327
+
328
+ # 验证进程状态
329
+ if self._is_valid_task_pid(pid):
330
+ # 获取实际日志路径
331
+ real_log_path = str(log_file.resolve(strict=True))
332
+ # 获取文件修改时间戳
333
+ mtime = log_file.stat().st_mtime
334
+ status = "Running" if self._is_pid_running(pid) else "Terminated"
335
+ task_info = (
336
+ {
337
+ "pid": pid,
338
+ "module": module,
339
+ "real_log": real_log_path,
340
+ "mtime": mtime,
341
+ "status": status,
342
+ }
343
+ )
344
+ tasks.append(task_info)
345
+ except (ValueError, IndexError) as e:
346
+ logging.error(f"Error parsing log file {log_file}: {e}")
347
+ continue
348
+ # 按修改时间降序排列(最新在前)
349
+ tasks.sort(key=lambda t: t["mtime"], reverse=True)
350
+ return tasks
351
+
352
+ def main_menu(self):
353
+ """主菜单循环 - Main menu loop"""
354
+ while True:
355
+ os.system("clear" if os.name == "posix" else "cls")
356
+ print("========== Task Execution Sys1tem ==========")
357
+ print(f"Current Version: {self.version}")
358
+ print(f"Current Environment: {self.env}")
359
+ print(f"Current Directory: {self.workspace}")
360
+ print(f"Log Base Directory: {self.log_dir}")
361
+ print("=" * 50)
362
+ print("1) Run EE Module")
363
+ print("2) Run CSP Module")
364
+ print("3) View Logs")
365
+ print("4) Terminate Tasks")
366
+ print("q) Exit")
367
+ print("=" * 50)
368
+
369
+ choice = input("Please enter operation: ").strip().lower()
370
+ if choice == "1":
371
+ work_dir = input("Enter EE working directory: ").strip()
372
+ self.task_runner("EE", work_dir)
373
+ input("\nPress Enter to continue...")
374
+ elif choice == "2":
375
+ work_dir = input("Enter CSP working directory: ").strip()
376
+ self.task_runner("CSP", work_dir)
377
+ input("\nPress Enter to continue...")
378
+ elif choice == "3":
379
+ self.view_logs()
380
+ elif choice == "4":
381
+ self.safe_terminate()
382
+ elif choice == "q":
383
+ print("\033c", end="") # 清屏指令
384
+ sys.exit(0)
385
+ else:
386
+ print("Invalid selection")
387
+ input("\nPress Enter to continue...")
388
+
389
+
390
+ def main():
391
+ manager = TaskManager()
392
+ manager.main_menu()
393
+
394
+
395
+ if __name__ == "__main__":
396
+ main()
@@ -224,7 +224,7 @@ class VaspProcessing:
224
224
  logging.error(
225
225
  f" Unfinished fine optimization job of CONTCAR_{mlp_density}_{number}"
226
226
  )
227
- fine_density, fine_energy = False, False
227
+ fine_density, fine_energy, molecules_flag = False, False, False
228
228
 
229
229
  # 读取根目录下的 config.yaml 信息与对应的 .json 文件
230
230
  config_path = os.path.join(self.base_dir, "config.yaml")
@@ -245,8 +245,8 @@ class VaspProcessing:
245
245
  packing_coefficient = round(
246
246
  molecular_volumes / fine_atoms_volume, 4
247
247
  )
248
- except FileNotFoundError:
249
- raise
248
+ except (FileNotFoundError, UnboundLocalError):
249
+ packing_coefficient = False
250
250
 
251
251
  mlp_energies.append(mlp_energy)
252
252
  rough_densities.append(rough_density)
@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: ion_CSP
3
+ Version: 2.0.4
4
+ Summary: Crystal Structure Design Software Based on Molecular/Ionic Configuration.
5
+ Home-page: https://github.com/bagabaga007/ion_CSP
6
+ Author: Ze Yang
7
+ Author-email: Ze Yang <yangze1995007@163.com>
8
+ License-Expression: MIT
9
+ Keywords: ion_crystal, crystal_structure_prediction
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: ase==3.23.0
16
+ Requires-Dist: deepmd-kit==3.0.1
17
+ Requires-Dist: torch==2.5.0
18
+ Requires-Dist: dpdispatcher==0.6.7
19
+ Requires-Dist: numpy==1.26.4
20
+ Requires-Dist: paramiko==3.5.1
21
+ Requires-Dist: pyxtal==1.0.4
22
+ Requires-Dist: phonopy==2.28.0
23
+ Requires-Dist: rdkit==2024.3.3
24
+ Requires-Dist: pytest==8.3.4
25
+ Dynamic: author
26
+ Dynamic: home-page
27
+ Dynamic: license-file
28
+ Dynamic: requires-python
29
+
30
+ # Crystal Structure Design Software V2.0
31
+
32
+ ## 项目概述
33
+ 基于分子/离子构型的晶体结构设计软件通过结合经验公式、机器学习势函数微调、第一性原理分步优化和分子/离子识别技术,实现了从分子/离子构型出发的高效晶体结构筛选。该软件采用模块化设计,支持全流程自动化材料筛选,在保证预测精度的同时显著提升计算效率。
34
+
35
+ ## 功能特性
36
+ ### 核心功能
37
+ - **双模块工作流**
38
+ - **EE模块**:基于经验评估的离子组合生成
39
+ - **CSP模块**:基于离子晶体结构预测的优化筛选
40
+ - **智能任务管理**
41
+ - 实时进程监控(PID跟踪)
42
+ - 日志文件自动符号链接
43
+ - 进程安全终止与资源清理
44
+ - **高级日志系统**
45
+ - 分页浏览(10条/页)
46
+ - 模块过滤(CSP/EE)
47
+ - 软链接解析显示实际路径
48
+
49
+ ### 技术特性
50
+ - 跨平台支持(Linux/Docker)
51
+ - 基于Python 3.11+的面向对象架构
52
+ - 集成psutil进程管理
53
+ - 结构化日志记录系统
54
+
55
+ ## 安装指南
56
+ ### 环境要求
57
+ | 组件 | 最低版本 |
58
+ |-------------|----------|
59
+ | Python | 3.11 |
60
+ | psutil | 5.9 |
61
+ | Docker | 20.10 |
62
+ | ase | 3.23.0 |
63
+ | deepmd-kit | 3.0.1 |
64
+ | torch | 2.5.0 |
65
+ | dpdispatcher| 0.6.7 |
66
+ | numpy | 1.26.4 |
67
+ | paramiko | 3.5.1 |
68
+ | pyxtal | 1.0.4 |
69
+ | phonopy | 2.28.0 |
70
+ | rdkit | 2023.03.3|
71
+
72
+ ### 安装步骤
73
+ ```bash
74
+ # 创建虚拟环境
75
+ python -m venv venv
76
+ source venv/bin/activate # Linux/Mac
77
+
78
+ # 安装依赖
79
+ pip install ion-csp
80
+ ```
81
+
82
+ ## 快速入门
83
+ ### 交互模式
84
+ ```bash
85
+ ion-csp
86
+ ```
87
+ 启动交互式命令行界面,支持以下操作:
88
+ - 模块选择
89
+ - 日志查看
90
+ - 进程管理
91
+
92
+ ### 脚本调用
93
+ #### EE模块示例
94
+ ```bash
95
+ ./scripts/main_EE.sh examples/example_1
96
+ ```
97
+ 从SMILES表格生成离子组合
98
+
99
+ #### CSP模块示例
100
+ ```bash
101
+ ./scripts/main_CSP.sh examples/example_2
102
+ ```
103
+ 从离子组合生成并优化晶体结构
104
+
105
+ ## 技术架构
106
+ ```mermaid
107
+ graph TD
108
+ A[用户界面] --> B[任务管理器]
109
+ B --> C[EE模块]
110
+ B --> D[CSP模块]
111
+ B --> E[日志系统]
112
+ B --> F[任务调度]
113
+ C --> G[经验评估引擎]
114
+ D --> H[晶体预测引擎]
115
+ E --> I[结构化日志]
116
+ F --> J[进程终止]
117
+ ```
118
+
119
+ ## 贡献指南
120
+ 1. Fork仓库并创建特性分支
121
+ 2. 编写单元测试覆盖新功能
122
+ 3. 提交Pull Request时注明关联Issue
123
+ 4. 遵循PEP8代码规范
124
+
125
+ ## 许可证
126
+ 本项目采用MIT许可证,详见LICENSE文件。
127
+
128
+ ## 技术支持
129
+ - **文档更新**:2025年5月
130
+ - **最新版本**:v2.0.4
131
+ - **问题追踪**:https://github.com/bagabaga007/ion_CSP/issues
132
+
133
+ ---
134
+
135
+ # Crystal Structure Design Software V2.0
136
+
137
+ ## Project Overview
138
+ This software enables efficient crystal structure screening from molecular/ion configurations by integrating empirical formulas, tuned machine learning potentials, stepwise first-principles optimization, and molecular/ion recognition techniques. The modular architecture ensures extensibility and maintainability while maintaining prediction accuracy.
139
+
140
+ ## Key Features
141
+ ### Core Functionalities
142
+ - **Dual-Module Workflow**
143
+ - **EE Module**: Empirical evaluation-based ion combination generation
144
+ - **CSP Module**: Ion crystal structure prediction and optimization
145
+ - **Intelligent Task Management**
146
+ - Real-time process monitoring (PID tracking)
147
+ - Automatic log file symlink creation
148
+ - Safe process termination with resource cleanup
149
+ - **Advanced Logging System**
150
+ - Paginated log viewing (10 entries/page)
151
+ - Module-based filtering (CSP/EE)
152
+ - Symlink resolution for actual log paths
153
+
154
+ ### Technical Specifications
155
+ - Cross-platform support (Linux/Docker)
156
+ - Object-oriented architecture with Python 3.11+
157
+ - Integrated process management via psutil
158
+ - Structured logging system
159
+
160
+ ## Installation
161
+ ### Prerequisites
162
+ | Component | Min Version |
163
+ |-------------|-------------|
164
+ | Python | 3.11 |
165
+ | psutil | 5.9 |
166
+ | Docker | 20.10 |
167
+ | ase | 3.23.0 |
168
+ | deepmd-kit | 3.0.1 |
169
+ | torch | 2.5.0 |
170
+ | dpdispatcher| 0.6.7 |
171
+ | numpy | 1.26.4 |
172
+ | paramiko | 3.5.1 |
173
+ | pyxtal | 1.0.4 |
174
+ | phonopy | 2.28.0 |
175
+ | rdkit | 2023.03.3|
176
+
177
+ ### Installation Steps
178
+ ```bash
179
+ # Create virtual environment
180
+ python -m venv venv
181
+ source venv/bin/activate # Linux/Mac
182
+
183
+ # Install dependencies
184
+ pip install ion-csp
185
+ ```
186
+
187
+ ## Quick Start
188
+ ### Interactive Mode
189
+ ```bash
190
+ ion-csp
191
+ ```
192
+ Launches CLI interface with:
193
+ - Module selection
194
+ - Log management
195
+ - Process control
196
+
197
+ ### Script Execution
198
+ #### EE Module Example
199
+ ```bash
200
+ ./scripts/main_EE.sh examples/example_1
201
+ ```
202
+ Generates ion combinations from SMILES tables
203
+
204
+ #### CSP Module Example
205
+ ```bash
206
+ ./scripts/main_CSP.sh examples/example_2
207
+ ```
208
+ Optimizes crystal structures from ion combinations
209
+
210
+ ## Technical Architecture
211
+ ```mermaid
212
+ graph TD
213
+ A[User Interface] --> B[Task Manager]
214
+ B --> C[EE Module]
215
+ B --> D[CSP Module]
216
+ B --> E[Log System]
217
+ B --> F[Task Scheduler]
218
+ C --> G[Empirical Evaluation Engine]
219
+ D --> H[Crystal Prediction Engine]
220
+ E --> I[Structured Logs]
221
+ F --> J[Process Termination]
222
+ ```
223
+
224
+ ## Contribution Guide
225
+ 1. Fork repository and create feature branch
226
+ 2. Write unit tests for new features
227
+ 3. Submit PR with issue reference
228
+ 4. Follow PEP8 coding standards
229
+
230
+ ## License
231
+ MIT License, see LICENSE file.
232
+
233
+ ## Support
234
+ - Documentation last updated: May 2025
235
+ - Latest version: v2.0.4
236
+ - Issue tracker: https://github.com/bagabaga007/ion_CSP/issues
@@ -0,0 +1,19 @@
1
+ ion_CSP/__init__.py,sha256=M5RcbYDdt-UBVpsDKiJWJqLvSFfoBfU6aTUVccgK_BE,190
2
+ ion_CSP/__main__.py,sha256=XlNCx5eMSrL7yld9ddSYXhjXvg2ZYGD_uk9LdqNabvs,74
3
+ ion_CSP/convert_SMILES.py,sha256=4fndMcuIEypYtkXWBoS7W7uEXkZXVLeMDshdXEIk5kY,13864
4
+ ion_CSP/empirical_estimate.py,sha256=_U5VRWSIAiJGcxnP3mnCHn8zKzviFEQhQwN7TjPTnCU,29089
5
+ ion_CSP/gen_opt.py,sha256=aVZmf2RqePCwZShgpNvzqfntNAW0I0yctWHGXoe3mgw,19463
6
+ ion_CSP/identify_molecules.py,sha256=hFKXS0Jjd7LyMsYGc9RmnoRPu1ibXF9fYO_9lR3wTfo,4634
7
+ ion_CSP/log_and_time.py,sha256=6RKegXF8Gc1HqzAbE-PT9ejX3Ncyuz3v3FivujJt8to,9072
8
+ ion_CSP/mlp_opt.py,sha256=ox4Qxg4D6WzrB8dxVnUWmAngnOA_wdcInP5UhBWsH4c,5535
9
+ ion_CSP/read_mlp_density.py,sha256=ktGNHYWLi4VrdJK_Hg2ac2-JM2QlVt9iymLxdixkLb8,9663
10
+ ion_CSP/steps_opt_monitor.sh,sha256=1klPjnK0gqkDbvI9PtjdK5qidJ5G0Mo8q1SfrlLW5xM,3330
11
+ ion_CSP/task_manager.py,sha256=PGtlvlJb7uzb1YcJyhP7cURQqOO4nNM-VacyZwVXixs,16088
12
+ ion_CSP/upload_download.py,sha256=Hiz5jKOy9x26hJJdcpt-owQdVUbzzGuGOelro6JozY8,23801
13
+ ion_CSP/vasp_processing.py,sha256=eQTMKIPBzi1X4HHTqWkHE1Lt8dvCCV-MXPCR3RtKYZ0,13977
14
+ ion_csp-2.0.4.dist-info/licenses/LICENSE,sha256=2J6A8GT2iIf2LhuWO1_0ilgx7ijzzpQ2BXU7rHKe8Cc,1068
15
+ ion_csp-2.0.4.dist-info/METADATA,sha256=r9ijfq_Yo5mTqMnCzNT9vYVF-FDSxN9bkHtU99FDdUw,6380
16
+ ion_csp-2.0.4.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
17
+ ion_csp-2.0.4.dist-info/entry_points.txt,sha256=NexQJDs9f69kJA2DgoU6tsA3V8a66nadJRem1U_c_6g,54
18
+ ion_csp-2.0.4.dist-info/top_level.txt,sha256=aYZa43dDebjLpWPN6bDIlBb6BVwA8gk4ajEjDDK9b9I,8
19
+ ion_csp-2.0.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ion-csp = ion_CSP.task_manager:main
ion_CSP/app.py DELETED
@@ -1,201 +0,0 @@
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()
@@ -1,83 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ion_CSP
3
- Version: 2.0.2
4
- Summary: Crystal Structure Design Software Based on Molecular/Ionic Configuration.
5
- Home-page: https://github.com/bagabaga007/ion_CSP
6
- Author: yangze
7
- Author-email: Ze Yang <yangze1995007@163.com>
8
- License: MIT License
9
-
10
- Copyright (c) [2025] [ion_CSP]
11
-
12
- Permission is hereby granted, free of charge, to any person obtaining a copy
13
- of this software and associated documentation files (the "Software"), to deal
14
- in the Software without restriction, including without limitation the rights
15
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
- copies of the Software, and to permit persons to whom the Software is
17
- furnished to do so, subject to the following conditions:
18
-
19
- The above copyright notice and this permission notice shall be included in all
20
- copies or substantial portions of the Software.
21
-
22
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
- SOFTWARE.
29
-
30
- Keywords: ion_crystal, crystal_structure_prediction
31
- Classifier: Programming Language :: Python :: 3
32
- Classifier: License :: OSI Approved :: MIT License
33
- Classifier: Operating System :: OS Independent
34
- Requires-Python: >=3.11
35
- Description-Content-Type: text/markdown
36
- License-File: LICENSE
37
- Requires-Dist: ase==3.23.0
38
- Requires-Dist: deepmd-kit==3.0.1
39
- Requires-Dist: torch==2.5.0
40
- Requires-Dist: dpdispatcher==0.6.7
41
- Requires-Dist: numpy==1.26.4
42
- Requires-Dist: paramiko==3.5.1
43
- Requires-Dist: pyxtal==1.0.4
44
- Requires-Dist: phonopy==2.28.0
45
- Requires-Dist: rdkit==2023.03.3
46
- Dynamic: author
47
- Dynamic: home-page
48
- Dynamic: license-file
49
- Dynamic: requires-python
50
-
51
- # 基于分子/离子构型的晶体结构设计软件 V2.0
52
-
53
- 基于分子/离子构型的晶体结构设计软件可以从分子/离子构型出发,结合经验公式、微调的机器学习势、第一性原理分步优化以及分子/离子识别方法,可以高效且有导向地筛选同时满足结构要求和性质要求的分子/离子晶体结构。该软件实现了分子/离子晶体结构的设计、构建、分析、筛选、评估全流程的自动化与模块化,保证了流程的扩展性和可重用性,能够在保证结果精度的同时,显著提高材料筛选效率。在整个流程中提供了多种检查功能以及结果可视化功能,方便用户使用。该软件使用Python语言编写,提供了完整的面向对象编程支持,在具有较高运行效率的同时使程序更易于理解、扩展和维护。
54
-
55
- # 安装
56
-
57
- 在项目根目录下使用以下命令安装依赖:
58
-
59
- pip install -r requirements.txt
60
-
61
- pip install -e .
62
-
63
- chmod +x ./scripts/*.sh
64
-
65
- # 使用示例
66
-
67
- ### 使用交互式功能自主选择
68
- ./scripts/main.sh
69
-
70
- #### 使用经验评估(EE)模块从SMILES表格开始生成离子组合
71
- ./scripts/main_EE.sh examples/example_1
72
-
73
-
74
- #### 使用离子晶体结构预测(CSP)模块从离子组合开始生成晶体并筛选和优化
75
- ./scripts/main_CSP.sh examples/example_2
76
-
77
- # 功能
78
- 使用经验评估(EE)模块从SMILES表格开始生成离子组合。
79
-
80
- 使用离子晶体结构预测(CSP)模块从离子组合开始生成晶体并筛选和优化。
81
-
82
- # 联系
83
- 如有问题,请联系 yangze1995007@163.com。
@@ -1,18 +0,0 @@
1
- ion_CSP/__init__.py,sha256=M5RcbYDdt-UBVpsDKiJWJqLvSFfoBfU6aTUVccgK_BE,190
2
- ion_CSP/app.py,sha256=MnuVdzV6yiTG__SPuWeQ05V0rwYqoJtez2XpdmRZOrU,6996
3
- ion_CSP/convert_SMILES.py,sha256=4fndMcuIEypYtkXWBoS7W7uEXkZXVLeMDshdXEIk5kY,13864
4
- ion_CSP/empirical_estimate.py,sha256=_U5VRWSIAiJGcxnP3mnCHn8zKzviFEQhQwN7TjPTnCU,29089
5
- ion_CSP/gen_opt.py,sha256=i9BxV6881YEO5PQZfYqu4wpJ_JfDfTqn0o2XJ90s9UU,19463
6
- ion_CSP/identify_molecules.py,sha256=yzLMWpGW1YM1B4a9cg4Jd1CekTWtXsPWAt-TpwL1Rks,4638
7
- ion_CSP/log_and_time.py,sha256=6RKegXF8Gc1HqzAbE-PT9ejX3Ncyuz3v3FivujJt8to,9072
8
- ion_CSP/mlp_opt.py,sha256=ox4Qxg4D6WzrB8dxVnUWmAngnOA_wdcInP5UhBWsH4c,5535
9
- ion_CSP/read_mlp_density.py,sha256=ktGNHYWLi4VrdJK_Hg2ac2-JM2QlVt9iymLxdixkLb8,9663
10
- ion_CSP/steps_opt_monitor.sh,sha256=1klPjnK0gqkDbvI9PtjdK5qidJ5G0Mo8q1SfrlLW5xM,3330
11
- ion_CSP/upload_download.py,sha256=Hiz5jKOy9x26hJJdcpt-owQdVUbzzGuGOelro6JozY8,23801
12
- ion_CSP/vasp_processing.py,sha256=Pd-_muPDMst-DKj6vGR34e7IFWsSyGiP7q-aMGZXatA,13911
13
- ion_csp-2.0.2.dist-info/licenses/LICENSE,sha256=2J6A8GT2iIf2LhuWO1_0ilgx7ijzzpQ2BXU7rHKe8Cc,1068
14
- ion_csp-2.0.2.dist-info/METADATA,sha256=OfnWFxuY9bXCwIpbkwJVqh8ZQyM1l78YWr6k44ZmVFk,3719
15
- ion_csp-2.0.2.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
16
- ion_csp-2.0.2.dist-info/entry_points.txt,sha256=l8q_8U8whrkzO1-8RIppbDco-520Cy3Pq1N5LyLTTLc,49
17
- ion_csp-2.0.2.dist-info/top_level.txt,sha256=aYZa43dDebjLpWPN6bDIlBb6BVwA8gk4ajEjDDK9b9I,8
18
- ion_csp-2.0.2.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- gen_opt = scripts.gen_opt:main