# yn-alkemie **Repository Path**: ningyoung99/yn-alkemie ## Basic Information - **Project Name**: yn-alkemie - **Description**: ppppp908098908908908 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-07 - **Last Updated**: 2026-01-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# `Alkemie` A modern computational materials science workflow platform designed for High Performance Computing (HPC) environments. ## 🎯 核心特性 - **📊 工作流编辑器** - 基于 ReactFlow 的可视化拖拽式工作流设计 - **🧬 结构解析** - 支持 CIF、POSCAR、XYZ 等多种文件格式 - **⚡ 远程作业执行** - PBS/Slurm 集群作业管理系统集成 - **🔌 模块化插件** - 可扩展的软件接口架构 - 🚀 **直接文件访问**:后端直接操作本地文件系统,无需 SSH/SFTP 传输 - ⚡ **实时状态推送**:WebSocket 主动推送作业状态,无需轮询 - 👥 **多用户支持**:支持多个用户同时访问和操作 - 🔄 **后台监控**:自动监控作业队列状态,10秒刷新周期 - **📈 智能结果可视化** - 上下文感知的计算结果图表展示 ## 🏗️ 系统架构 Alkemie v2.0 采用**服务器端部署 + WebSocket 实时通信**架构,后端服务运行在 HPC 上,用户通过浏览器访问,提供了更快的响应速度、更低的网络延迟和多用户并发支持。 ``` ┌─────────────────────────────────────────────────────────────┐ │ Web Browser (Client) │ ├─────────────────────────────────────────────────────────────┤ │ Frontend (React 18) │ │ ├── Ant Design 5.x (UI Components) │ │ ├── ReactFlow 11.x (Workflow Editor) │ │ ├── Three.js (3D Visualization) │ │ ├── Recharts (Data Visualization) │ │ ├── Socket.IO Client (WebSocket) │ │ └── Zustand (State Management) │ ├─────────────────────────────────────────────────────────────┤ │ ⬍ WebSocket (Real-time) ⬍ │ ├─────────────────────────────────────────────────────────────┤ │ Backend Server (Flask + Flask-SocketIO) │ │ ├── REST API Server (HTTP/HTTPS) │ │ ├── WebSocket Manager (Real-time Communication) │ │ ├── Local File Manager (Direct File Operations) │ │ ├── Background Monitor (Job Status Tracking) │ │ ├── Software Interfaces │ │ ├── Result Parsing & Analysis │ │ ├── Workflow Execution │ │ └── AI Agents (Intelligent assistance) │ ├─────────────────────────────────────────────────────────────┤ │ HPC System (Server-Side Deployment) │ │ ├── PBS/Slurm Queue Management │ │ ├── Computational Software (VASP, QE, etc.) │ │ ├── Local File System Access │ │ └── Job Submission & Monitoring │ └─────────────────────────────────────────────────────────────┘ ``` ## 📡 数据流架构 ### 整体数据流模式 Alkemie v2.0 采用**服务器端部署 + WebSocket 实时通信**架构,数据流分为三个主要层次: ``` 用户浏览器 ←→ WebSocket ←→ Flask后端 ←→ 本地文件系统 ←→ HPC队列系统 ``` ### 1. 项目创建与配置流程 ``` 1. 用户操作(浏览器) ↓ HTTP POST /api/projects 2. Flask后端接收请求 ↓ 创建项目目录结构 3. 本地文件系统 ↓ ~/alkemie-workspaces/alkemie-projects/project_xxx/ 4. 返回项目信息 ↓ HTTP Response 5. 前端更新状态 ``` ### 2. 工作流保存与输入文件生成 ``` 1. 用户编辑工作流(ReactFlow画布) ↓ 拖拽节点、配置参数、连接边 2. 保存工作流 ↓ HTTP PUT /api/projects/ 3. Flask后端处理 ↓ 解析工作流节点和边 ↓ 调用软件接口生成输入文件 4. 软件接口(VASPInterface/GNEPInterface等) ↓ generate_inputs_for_node() ↓ 生成 INCAR, POSCAR, KPOINTS, POTCAR, submit.pbs 5. 写入本地文件系统 ↓ ~/alkemie-workspaces/alkemie-projects/project_xxx/relax/ 6. 更新 project_info.json ↓ 保存节点配置、边信息 7. WebSocket 广播 ↓ ws_manager.broadcast_project_status() 8. 前端实时更新 ↓ 显示"已保存"状态 ``` ### 3. 工作流执行流程(实时通信核心) ``` 1. 用户点击"执行工作流" ↓ HTTP POST /api/workflows/execute 2. Flask后端处理 ↓ workflow_manager.submit_job() 3. 生成作业脚本并提交 ↓ qsub submit.pbs(PBS)或 sbatch submit.pbs(Slurm) 4. 获取作业ID ↓ 更新 job_info.json 5. WebSocket 实时推送 ↓ ws_manager.broadcast_project_status(project_id, {'status': 'running', 'job_id': xxx}) 6. 前端实时接收 ↓ Socket.IO 客户端监听 'project_status_changed' 事件 ↓ 更新UI:显示"运行中"状态和作业ID ``` ### 4. 后台监控与状态更新(自动化) ``` 后台监控线程(background_monitor.py)每10秒执行: ↓ 1. 扫描所有项目目录 ↓ os.listdir(~/alkemie-workspaces/alkemie-projects/) 2. 读取 job_info.json ↓ 检查作业状态 3. 查询队列系统 ↓ qstat(PBS)或 squeue(Slurm) 4. 检测状态变化 ↓ 运行中 → 完成 / 失败 5. 解析输出文件(如果完成) ↓ VASPInterface.parse_results() ↓ 读取 OUTCAR, vasprun.xml 等 6. WebSocket 实时推送 ↓ ws_manager.broadcast_job_status() ↓ ws_manager.broadcast_node_status() 7. 前端自动更新 ↓ 显示计算完成、能量、收敛状态等 ``` ### 5. 结果查看流程 ``` 1. 用户点击"查看结果" ↓ HTTP GET /api/projects//nodes//results 2. Flask后端 ↓ workflow_manager.get_node_results() 3. 软件接口解析 ↓ VASPInterface.parse_results() ↓ 读取 vasprun.xml, DOSCAR, EIGENVAL ↓ 提取能量、DOS、能带等数据 4. 返回JSON数据 ↓ {success: true, results: {energy: -123.45, dos_data: [...], ...}} 5. 前端渲染可视化 ↓ NodeComponent 调用 createVASPChartConfig() ↓ Recharts 绘制 DOS、能带图 ↓ Three.js 渲染 3D 结构 ``` ### 6. WebSocket 事件订阅机制 **前端订阅流程**: ```javascript // 1. 连接到 WebSocket 服务器 websocketClient.connect('https://localhost:8443'); // 2. 订阅特定项目更新 websocketClient.subscribeProject(projectId); // 3. 监听实时事件 websocketClient.on('job_status', (data) => { // 自动更新作业状态 }); websocketClient.on('project_status', (data) => { // 自动更新项目状态 }); websocketClient.on('node_result', (data) => { // 自动刷新节点结果 }); ``` **后端推送机制**: ```python # 广播到订阅了该项目的所有客户端 ws_manager.broadcast_job_status( project_id='project_20250107_120000', job_data={'status': 'completed', 'job_id': '12345'} ) ``` ### 7. 文件上传流程 ``` 1. 用户拖拽文件到上传区 ↓ FormData 封装文件 2. HTTP POST /api/upload ↓ multipart/form-data 3. Flask后端接收 ↓ file.save(~/alkemie-workspaces/alkemie-uploads/xxx.cif) 4. 结构解析 ↓ StructureParser.parse_structure() ↓ 使用 ASE 库解析 5. 返回结构信息 ↓ {lattice: [...], atoms: [...], formula: 'Si2', ...} 6. 可选:移动到项目目录 ↓ ~/alkemie-workspaces/alkemie-projects/project_xxx/structures/ 7. WebSocket 通知 ↓ ws_manager.broadcast_file_upload_progress() ``` ### 8. 数据缓存策略 为提升性能,后端使用多层缓存: ```python # TTL缓存(时间限制缓存) remote_projects_cache = TTLCache(default_ttl=60) # 项目列表缓存60秒 queue_info_cache = TTLCache(default_ttl=30) # 队列信息缓存30秒 system_perf_cache = TTLCache(default_ttl=10) # 系统性能缓存10秒 # Singleflight去重(防止并发重复请求) @singleflight def expensive_operation(): # 多个并发请求会合并为一次执行 pass ``` **缓存更新策略**: - **后台软刷新**: 缓存接近过期时(剩余3秒),后台自动刷新 - **强制刷新**: 用户可通过 `?refresh=true` 参数强制刷新 - **WebSocket主动推送**: 状态变化时主动推送,无需轮询 ### 9. 关键数据流总结 | 数据类型 | 传输方式 | 频率 | 缓存策略 | |---------|---------|------|---------| | 项目列表 | HTTP GET | 按需 | 60秒TTL + 软刷新 | | 工作流保存 | HTTP PUT | 用户操作 | 无缓存(实时写入) | | 作业提交 | HTTP POST | 用户操作 | 无缓存(实时提交) | | 作业状态 | WebSocket 推送 | 状态变化时 | 后台监控自动推送 | | 队列信息 | HTTP GET + WebSocket | 每30秒 | 30秒TTL | | 系统性能 | HTTP GET + WebSocket | 每10秒 | 10秒TTL | | 计算结果 | HTTP GET | 按需 | 无缓存(解析后直接返回) | ## 🚀 Quick Start ### Step 1: Precondition ✅ Upload Files to HPC ✅ Prepare Your Anaconda/Miniconda(recommend) Env ### Step 2: Run Deployment Script ```bash cd your-path-to/Alkemie-master/backend chmod +x deploy.sh ./deploy.sh ``` The script will: - ✅ Check environment (Conda, queue system) - ✅ Create directory structure - ✅ Set up Python virtual environment - ✅ Install dependencies - ✅ Generate SSL certificates - ✅ Build React frontend - ✅ Create systemd user service - ✅ Start the server ### Step 3: Set Up SSH Tunnel (if connect fail) ```bash # On local machine (keep this terminal open) ssh -L 8443:localhost:8443 username@hpc.example.edu ``` ### Step 4: Access Alkemie Open browser and navigate to: ``` https://localhost:8443 ``` **Note**: You'll see a security warning about the self-signed certificate. This is expected - click "Advanced" and "Proceed" to continue. --- ## 📖 Detailed Installation ### 1. Pre-Installation Checks #### Check Python Version ```bash python3 --version # Should be 3.8 or higher ``` #### Check Queue System ```bash # For PBS which qsub qstat --version # For Slurm which sbatch squeue --version ``` #### Check Available Disk Space ```bash df -h ~ # Make sure you have at least 10GB free (enough) ``` ## 📖 Detailed Installation ### 1. Pre-Installation Checks #### Check Python Version ```bash python3 --version # Should be 3.8 or higher ``` #### Check Queue System ```bash # For PBS which qsub qstat --version # For Slurm which sbatch squeue --version ``` #### Check Available Disk Space ```bash df -h ~ # Should have at least 10GB free ``` ### 2. Directory Structure #### 2.1 部署目录结构 (Deployment Structure) 部署脚本会创建以下运行时目录结构: ``` ~/alkemie-workspaces/ ├── alkemie-server/ # 服务器文件 (Server files) │ ├── app.py # 主服务器应用 (Main server application) │ ├── local_file_manager.py # 本地文件管理器 (Local file manager) │ ├── websocket_manager.py # WebSocket 连接管理 (WebSocket manager) │ ├── background_monitor.py # 后台作业监控 (Background job monitor) │ ├── software/ # 计算软件接口 (Software interfaces) │ │ ├── vasp/ # VASP 接口 │ │ ├── gnep/ # GNEP 接口 │ │ ├── qe/ # Quantum ESPRESSO 接口 │ │ ├── base_interface.py # 接口基类 │ │ ├── decorators.py # 通用装饰器 │ │ ├── registry.py # 软件注册表 │ │ └── schema_service.py # Schema 加载服务 │ ├── shared/ # 共享资源 (Shared resources) │ │ └── schemas/ # 组件定义 JSON Schema (⭐ Schema-Driven) │ │ └── vasp_parameters.json │ ├── build/ # React 前端构建文件 (Frontend build) │ ├── cert.pem # SSL 证书 (SSL certificate) │ ├── key.pem # SSL 私钥 (SSL private key) │ ├── start.sh # 启动脚本 (Startup script) │ └── .env # 环境变量配置 (Environment config) ├── alkemie-projects/ # 所有项目数据 (All project data) │ ├── project_20250104_120000/ │ │ ├── project_info.json # 项目元信息 │ │ ├── relax/ # 结构优化计算目录 │ │ │ ├── INCAR, POSCAR, KPOINTS, POTCAR │ │ │ ├── job_info.json # 作业状态信息 │ │ │ └── OUTCAR, vasprun.xml # 计算结果 │ │ └── scf/ # 自洽计算目录 │ └── project_20250104_130000/ ├── alkemie-uploads/ # 临时上传暂存区 (Upload staging) └── alkemie-logs/ # 服务器日志 (Server logs) ├── server.log ├── systemd.log └── systemd-error.log ``` #### 2.2 开发源码结构 (Development Source Structure) 开发时的源码目录结构(**重要:开发者必读**): ``` alkemie-v2.0.x/ ├── src/ # React 前端源码 │ ├── components/ │ │ ├── software/ # 软件组件库 (⭐ 开发重点) │ │ │ ├── vasp/ # VASP 组件 │ │ │ │ ├── VaspComponents.js # UI 配置 (color, icon, defaultConfig) │ │ │ │ ├── VaspConfigForm.js # 参数表单渲染器 │ │ │ │ └── VaspTemplates.js # 工作流模板 │ │ │ ├── gnep/ # GNEP 组件 (静态定义) │ │ │ ├── qe/ # QE 组件 (静态定义) │ │ │ └── softwareConfig.js # 组件注册中心 │ │ ├── workflow/ # 工作流编辑器 │ │ │ ├── WorkflowEditor.js # 主编辑器组件 │ │ │ ├── NodeComponent.js # 节点组件 │ │ │ └── ComponentLibrary.js # 组件库面板 │ │ └── sidebar/ # 侧边栏组件 │ │ └── SoftwareCenter.js # 软件中心 │ ├── hooks/ # React Hooks │ │ └── useComponents.js # 动态组件加载 Hook (⭐ Schema-Driven 核心) │ └── utils/ │ └── websocket.js # WebSocket 客户端 ├── shared/ # 前后端共享资源 (⭐ Schema-Driven) │ └── schemas/ # JSON Schema 定义 │ ├── vasp_parameters.json # VASP 组件和参数定义 │ └── [software]_parameters.json # 其他软件 Schema ├── backend/ # Flask 后端源码 │ ├── app.py # 主应用和 API 路由 │ ├── remote_workflow_manager.py # 工作流执行管理器 │ ├── software/ # 软件接口 (⭐ 开发重点) │ │ ├── __init__.py # 接口导出 │ │ ├── base_interface.py # 接口基类 │ │ ├── decorators.py # 通用装饰器 │ │ ├── registry.py # 软件注册表 │ │ ├── schema_service.py # Schema 服务 │ │ ├── vasp/ │ │ │ └── vasp_interface.py # VASP 计算接口 │ │ └── gnep/ │ │ └── gnep_interface.py # GNEP 计算接口 │ └── software_config.json # 软件环境配置 └── public/ └── logo/ # 软件 Logo 资源 ``` > **⭐ Schema-Driven 架构说明**:从 v2.0.2 开始,VASP 组件采用 Schema-Driven 架构。组件元数据(名称、描述、参数)集中定义在 `shared/schemas/` 中,前端仅定义 UI 配置(颜色、图标)。详见开发手册。 > #### 📖 Schema-Driven 架构设计 ```bash ┌─────────────────────────────────────────────────────────────┐ │ Shared Schema Layer │ │ shared/schemas/{software}_parameters.json │ │ - 参数定义、类型、约束、UI提示、验证规则 │ └────────────────────────┬────────────────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Frontend │ │ Backend │ │ AI Agent │ │ SchemaForm │ │ SoftwareIF │ │ Query │ └─────────────┘ └──────────────┘ └─────────────┘ ``` ### 3. Deployment Options #### Standard Deployment ```bash ./deploy.sh ``` #### Custom Port ```bash ./deploy.sh --port 9000 ``` #### Skip Frontend Build (if already built) ```bash ./deploy.sh --skip-build ``` #### Skip SSL Generation (use existing certificates) ```bash ./deploy.sh --skip-ssl ``` #### Combined Options ```bash ./deploy.sh --port 9000 --skip-build ``` ## 🔧 Configuration ### Environment Variables Edit `~/alkemie-workspaces/alkemie-server/.env`: ```bash # Server Configuration ALKEMIE_PORT=8443 # Server port ALKEMIE_HOST=0.0.0.0 # Bind address ALKEMIE_BASE_DIR=~/alkemie-workspaces/alkemie-projects # Projects directory ALKEMIE_UPLOADS_DIR=~/alkemie-workspaces/alkemie-uploads # Uploads directory ALKEMIE_LOGS_DIR=~/alkemie-workspaces/alkemie-logs # Logs directory # Queue System (auto-detected) QUEUE_SYSTEM=PBS # or Slurm ``` ### Port Configuration If port 8443 is already in use: 1. **Change server port**: ```bash ./deploy.sh --port 9000 ``` 2. **Update SSH tunnel**: ```bash ssh -L 9000:localhost:9000 username@hpc.example.edu ``` 3. **Access at new port**: ``` https://localhost:9000 ``` ### SSL Certificates #### Using Self-Signed Certificates (Default) The deployment script generates self-signed certificates automatically. You'll need to accept the security warning in your browser. #### Using Custom Certificates Replace the generated certificates: ```bash cp your-cert.pem ~/alkemie-workspaces/alkemie-server/cert.pem cp your-key.pem ~/alkemie-workspaces/alkemie-server/key.pem systemctl --user restart alkemie ``` --- ## 🎮 Service Management ### Systemd User Service The deployment creates a systemd user service for automatic startup and management. #### Start Service ```bash systemctl --user start alkemie ``` #### Stop Service ```bash systemctl --user stop alkemie ``` #### Restart Service ```bash systemctl --user restart alkemie ``` #### Check Status ```bash systemctl --user status alkemie ``` #### Enable Auto-Start (on login) ```bash systemctl --user enable alkemie ``` #### Disable Auto-Start ```bash systemctl --user disable alkemie ``` #### View Logs ```bash # Real-time logs tail -f ~/alkemie-workspaces/alkemie-logs/server.log # Systemd logs journalctl --user -u alkemie -f # Error logs tail -f ~/alkemie-workspaces/alkemie-logs/systemd-error.log ``` ### Manual Service Management If systemd is not available, use the startup script directly: #### Start Server ```bash cd ~/alkemie-workspaces/alkemie-server ./start.sh & ``` #### Stop Server ```bash pkill -f "python app.py" ``` #### Check Server ```bash pgrep -f "python app.py" ``` #### Using tmux (Recommended) ```bash # Start in tmux session tmux new -s alkemie cd ~/alkemie-workspaces/alkemie-server ./start.sh # Detach: Ctrl+B, then D # Reattach later tmux attach -t alkemie ``` #### Using screen ```bash # Start in screen session screen -S alkemie cd ~/alkemie-workspaces/alkemie-server ./start.sh # Detach: Ctrl+A, then D # Reattach later screen -r alkemie ``` --- ## 🌐 SSH Tunnel Setup ### Basic Tunnel ```bash ssh -L 8443:localhost:8443 username@hpc.example.edu ``` **Keep this terminal open** while using Alkemie. ### Background Tunnel ```bash ssh -fN -L 8443:localhost:8443 username@hpc.example.edu ``` - `-f`: Run in background - `-N`: Don't execute remote command To close background tunnel: ```bash # Find process ID ps aux | grep "ssh.*8443" # Kill process kill ``` ## 📁 核心文件结构 - **`backend/app.py`** - API路由和软件接口注册中心 - **`backend/remote_workflow_manager.py`** - 工作流执行核心逻辑 - **`backend/software/`** - 计算软件接口实现 - **`src/components/software/`** - 前端软件组件库 - **`src/components/workflow/WorkflowEditor.js`** - 工作流编辑器核心 - **`src/components/workflow/NodeComponent.js`** - 工作流组件设计 - **`src/components/software/softwareConfig.js`** - 组件注册中心 ## 🔧 开发手册 - 拉取代码后在`dev`分支或者新建自己的分支上进行开发 - `push` 代码前记得 `fetch/pull` 仓库避免冲突 本手册提供详细的开发指南,面向两种开发场景: 1. **开发新软件** - 从零开始集成新计算软件 2. **开发软件新组件** - 扩展现有软件的功能 ### 📋 开发场景1:扩展现有软件功能 以 VASP 新增「分子动力学计算」组件为例,展示完整的扩展流程。 > **⭐ 核心原则:Schema-First 开发流程** > > Alkemie v2.0 采用 Schema-Driven 架构,组件的元数据(名称、描述、参数定义)统一在 JSON Schema 中管理,前端只负责 UI 配置(颜色、图标等)。因此,扩展功能的**第一步是定义 Schema,而不是直接写代码**。 --- #### Step 1: 定义组件 Schema(⭐ 必须第一步) **位置**: `shared/schemas/vasp_parameters.json` **操作**: 在 `components` 部分添加新组件定义 ```json { "components": { // ... 现有组件 ... "vasp_molecular_dynamics": { "id": "vasp_molecular_dynamics", "name": { "zh": "分子动力学", "en": "Molecular Dynamics" }, "description": { "zh": "执行从头算分子动力学模拟,研究原子尺度的动力学行为", "en": "Perform ab-initio molecular dynamics simulations to study atomic-scale dynamic behavior" }, "category": "md", "parameters": [ "ispin", "encut", "prec", "kpoints_config", "ismear", "sigma", "algo", "nelm", "ediff", "ibrion", "nsw", "potim", "tebeg", "teend", "smass", "isym", "ivdw" ] } }, "parameters": { // ... 现有参数 ... // 如果需要新参数(如 POTIM、TEBEG 等),也在这里定义 "potim": { "type": "number", "label": {"zh": "POTIM | 时间步长 (fs)", "en": "POTIM | Time Step (fs)"}, "default": 1.0, "constraints": {"min": 0.1, "max": 10.0, "step": 0.1}, "tooltip": { "zh": "分子动力学时间步长,单位飞秒(fs)", "en": "MD time step in femtoseconds" }, "incar_key": "POTIM", "category": "md" }, "tebeg": { "type": "number", "label": {"zh": "TEBEG | 起始温度 (K)", "en": "TEBEG | Start Temperature (K)"}, "default": 300, "tooltip": {"zh": "分子动力学起始温度", "en": "MD starting temperature"}, "incar_key": "TEBEG", "category": "md" } // ... 更多参数定义 ... } } ``` **关键说明**: - **`id`**: 组件唯一标识符,与后端 `get_calculation_type()` 返回值对应 - **`name/description`**: 支持 i18n 的中英文对照,前端自动提取 - **`category`**: 计算类型,也是工作目录名称(如 `md/`) - **`parameters`**: 该组件使用的参数列表,引用 `parameters` 部分定义的参数名 --- #### Step 2: 更新后端接口(继承 SoftwareInterface) **位置**: `backend/software/vasp/vasp_interface.py` > **重要提示**: VASP 接口已继承自 `SoftwareInterface` 基类并注册到 `SoftwareRegistry`。扩展组件只需修改以下部分: ##### 2.1 添加支持的计算类型 ```python @SoftwareRegistry.register('vasp') class VASPInterface(SoftwareInterface): SOFTWARE_NAME = "vasp" SUPPORTED_CALCULATION_TYPES = [ 'relax', 'scf', 'band', 'dos', 'structure_input', 'md' # ⭐ 添加新类型 ] ``` ##### 2.2 更新 `get_calculation_type()` 方法 ```python def get_calculation_type(self, node: Dict) -> Optional[str]: """从节点配置中提取计算类型(目录名)""" node_data = node.get('data', {}) node_type = node_data.get('nodeType', '') # 节点类型 -> 计算类型映射 # Map node type to calculation type type_mapping = { 'vasp_structure_optimization': 'relax', 'vasp_scf_calculation': 'scf', 'vasp_band_calculation': 'band', 'vasp_dos_calculation': 'dos', 'vasp_structure_input': 'structure_input', 'vasp_molecular_dynamics': 'md' # ⭐ 新增映射 } # other existing code ... return None ``` ##### 2.3 更新 `get_file_dependencies()` 方法 ```python def get_file_dependencies(self, calculation_type: str) -> List[Dict[str, str]]: """定义计算类型之间的文件依赖关系""" base_dependencies = [ {'source': 'CONTCAR', 'dest': 'POSCAR', 'type': 'copy'} ] calc_dependencies = { 'relax': [], 'scf': [ *base_dependencies, {'source': 'WAVECAR', 'dest': 'WAVECAR', 'type': 'link'}, {'source': 'CHGCAR', 'dest': 'CHGCAR', 'type': 'link'} ], 'band': [ *base_dependencies, {'source': 'WAVECAR', 'dest': 'WAVECAR', 'type': 'link'}, {'source': 'CHGCAR', 'dest': 'CHGCAR', 'type': 'link'} ], 'dos': [ *base_dependencies, {'source': 'WAVECAR', 'dest': 'WAVECAR', 'type': 'link'}, {'source': 'CHGCAR', 'dest': 'CHGCAR', 'type': 'link'} ], 'md': [ # ⭐ 新增分子动力学依赖 *base_dependencies, {'source': 'WAVECAR', 'dest': 'WAVECAR', 'type': 'link'} ] } return calc_dependencies.get(calculation_type, []) ``` **文件依赖说明**: - `type: 'copy'`: 复制文件(适用于小文件或需要修改的文件) - `type: 'link'`: 创建符号链接(适用于大文件如 WAVECAR、CHGCAR) ##### 2.4 更新 `parse_results()` 方法(可选) 如果新组件需要解析特定输出,在 `parse_results()` 中添加处理逻辑: ```python @log_execution @cache_result(ttl_seconds=120) def parse_results(self, manager, remote_calc_path: str, calculation_type: str) -> Dict[str, Any]: """解析计算结果""" results = {'available_files': []} # ... 现有解析逻辑 ... # ⭐ 新增分子动力学结果解析 if calculation_type == 'md': # 解析 OSZICAR 获取能量-时间轨迹 oszicar_path = f"{remote_calc_path}/OSZICAR" oszicar_result = manager.read_file_content(oszicar_path) if oszicar_result['success']: md_data = self._parse_md_trajectory(oszicar_result['content']) results['md_trajectory'] = md_data return results def _parse_md_trajectory(self, oszicar_content: str) -> Dict[str, Any]: """解析 OSZICAR 中的 MD 轨迹数据""" lines = oszicar_content.strip().split('\n') trajectory = {'steps': [], 'energy': [], 'temperature': []} for line in lines: if line.strip().startswith('T='): # 解析温度、能量等数据 # 示例: T= 300.0 E= -.12345E+02 F= -.12346E+02 parts = line.split() trajectory['steps'].append(len(trajectory['steps']) + 1) trajectory['temperature'].append(float(parts[1])) trajectory['energy'].append(float(parts[3])) return trajectory ``` ##### 2.5 更新 `generate_inputs()` 中的特定参数处理(可选) 在 `_format_incar()` 方法中添加针对分子动力学的固定参数: ```python def _format_incar(self, vasp_params: Dict[str, Any], calculation_type: str) -> str: """格式化 INCAR 文件内容""" # ... 通用参数处理 ... # ⭐ 分子动力学特定设置 if calculation_type == 'md': vasp_params.update({ 'IBRION': 0, # 分子动力学模式 'MDALGO': 2, # Nose-Hoover thermostat 'ISIF': 2, # 仅优化离子位置 'LCHARG': '.FALSE.', # 不写入 CHGCAR 'LWAVE': '.FALSE.' # 不写入 WAVECAR }) # ... 格式化为 INCAR 文本 ... ``` --- #### Step 3: 添加前端 UI 配置 **位置**: `src/components/software/vasp/VaspComponents.js` > **重要**: 前端**只定义 UI 配置**(颜色、图标、categoryName、defaultConfig),组件元数据(name、description、parameters)已在 Schema 中定义。 ```javascript export const VASP_UI_CONFIG = { // ... 现有组件 UI 配置 ... vasp_molecular_dynamics: { color: '#ff6b6b', // 组件颜色 icon: '🌡️', // 组件图标(Emoji 或自定义) categoryName: '动力学计算', // 侧边栏分类显示名称 defaultConfig: { // UI 级别的默认值(可选,作为 Schema 默认值的回退或者模板工作流参数) ibrion: '0', nsw: 1000, potim: 1.0, tebeg: 300, teend: 300, smass: 0, kpoints_config: { mode: 'spacing', kspacing: 0.5, kgamma: true } } } }; // 动态加载 Hook(已存在,无需修改) export const useVaspComponents = (options = {}) => { return useComponents('vasp', VASP_UI_CONFIG, options); }; ``` **关键说明**: - **`color`** 和 **`icon`**: 前端可视化配置 - **`categoryName`**: 侧边栏中文分类名(对应 Schema 中的 `category`) - **`defaultConfig`**: 可选的 UI 级默认值,会与 Schema 默认值合并(Schema 优先) --- #### Step 4: 前端表单渲染(可选,Schema 自动生成) **位置**: `src/components/software/vasp/VaspConfigForm.js` > **说明**: 由于 Schema-Driven 架构,大部分参数表单已自动生成。如果需要**自定义渲染**特定参数(如复杂的 tooltip、特殊校验),可在 `renderVaspConfigForm()` 中添加: ```javascript export const renderVaspConfigForm = (config, form, nodeType, software) => { const formItems = []; // ... 现有参数渲染逻辑 ... Object.entries(config).forEach(([key, value]) => { // ⭐ 自定义 POTIM 参数渲染(如果需要特殊提示) if (key === "potim") { formItems.push(
POTIM:分子动力学时间步长(单位:飞秒)。
建议值:
  • 常规 MD:1.0 - 2.0 fs
  • 高温/剧烈运动:0.5 - 1.0 fs
  • 含氢体系:0.5 fs(氢原子运动快)
} >
); } // ... 其他参数通用渲染 ... }); return formItems; }; ``` --- #### Step 5: 测试新组件 1. **重启后端服务**(如果修改了 Python 代码): ```bash # 如果使用 systemd systemctl --user restart alkemie # 或手动重启 cd ~/alkemie-workspaces/alkemie-server ./start.sh ``` 2. **前端热重载**(React 会自动重载,无需重启) 3. **验证流程**: - 打开浏览器:`https://localhost:8443` - 进入工作流编辑器 - 在左侧组件库中找到「分子动力学」组件(🌡️ 图标) - 拖拽到画布并配置参数 - 保存工作流,观察控制台是否有错误 - 提交作业并验证生成的 INCAR 文件是否正确 --- #### 扩展组件常见问题 **Q1: 如何确定 `parameters` 列表中应该包含哪些参数?** 参考 VASP 官方文档和现有组件,选择该计算类型必需的参数。例如: - 所有组件都需要:`encut`, `prec`, `kpoints_config`, `ediff` - 结构优化需要:`nsw`, `ibrion`, `isif`, `ediffg` - 分子动力学需要:`ibrion=0`, `nsw`, `potim`, `tebeg`, `teend` **Q2: `get_calculation_type()` 返回值和 Schema 中的 `category` 有什么关系?** 两者通常保持一致,都表示计算类型。`get_calculation_type()` 返回值决定: - 工作目录名称(如 `~/project_xxx/md/`) - 调用哪个文件依赖配置 - 解析结果时的分支逻辑 **Q3: 为什么前端不需要写组件的 name/description?** 因为采用 Schema-Driven 架构,这些元数据已在 `shared/schemas/vasp_parameters.json` 中定义,前端通过 `useComponents()` Hook 动态加载并合并 UI 配置。 **Q4: 如何调试新组件?** - **后端日志**: 查看 `~/alkemie-workspaces/alkemie-logs/server.log` - **前端控制台**: 浏览器 F12 → Console 查看加载和渲染错误 - **生成的输入文件**: 保存工作流后,检查 `~/alkemie-workspaces/alkemie-projects/project_xxx/md/` 目录下的 INCAR、KPOINTS、submit.pbs 是否正确 --- `★ Insight ─────────────────────────────────────` **Schema-Driven 架构的核心优势**: 1. **单一数据源**:组件元数据集中管理,前后端自动同步 2. **快速扩展**:添加新组件只需修改 JSON + 少量 Python 代码 3. **类型安全**:Schema 提供参数校验和默认值,减少运行时错误 4. **国际化**:天然支持多语言,无需额外配置 `─────────────────────────────────────────────────` ### 📋 开发场景2:集成新计算软件 以集成 **GNEP(机器学习势)** 为例,展示从零开始集成新计算软件的完整流程。GNEP 是一个复杂的软件,包含数据预处理、数据集创建和模型训练等多个环节。 > **⭐ 核心原则:Schema-First + SoftwareInterface 继承** > > 集成新软件的开发流程: > 1. 定义 JSON Schema(组件和参数) > 2. 实现后端接口(继承 `SoftwareInterface` 并实现 5 个抽象方法) > 3. 添加前端 UI 配置 > 4. 注册并测试 --- #### Step 1: 定义 GNEP Schema(⭐ 必须第一步) **位置**: `shared/schemas/gnep_parameters.json` **操作**: 创建完整的 GNEP 组件和参数定义 ```json { "version": "1.0", "software": "gnep", "description": { "zh": "Gradient-optimized Neuroevolution Potential - 机器学习势函数训练和预测", "en": "Gradient-optimized Neuroevolution Potential - ML potential training and prediction" }, "parameters": { "prediction": { "type": "boolean", "label": {"zh": "预测模式", "en": "Prediction Mode"}, "default": false, "tooltip": { "zh": "是否进行预测(训练模式设置为 false)", "en": "Whether to perform prediction (set to false for training mode)" }, "incar_key": "prediction" }, "cutoff": { "type": "array", "label": {"zh": "截断半径 (Å)", "en": "Cutoff Radius (Å)"}, "itemType": "number", "length": 2, "default": [4.0, 4.5], "tooltip": { "zh": "两个截断半径:内截断和外截断", "en": "Two cutoff radii: inner and outer cutoff" } }, "n_max": { "type": "array", "label": {"zh": "径向基函数数量", "en": "Radial Basis Functions"}, "itemType": "number", "length": 2, "default": [8, 8] }, "l_max": { "type": "number", "label": {"zh": "角动量最大值", "en": "Maximum Angular Momentum"}, "default": 4, "constraints": {"min": 0, "max": 10} }, "basis_size": { "type": "array", "label": {"zh": "基组大小", "en": "Basis Size"}, "itemType": "number", "length": 2, "default": [15, 15] }, "neuron": { "type": "string", "label": {"zh": "神经网络结构", "en": "Neural Network Structure"}, "default": "30", "tooltip": { "zh": "神经网络隐藏层结构,例如:30 表示30个神经元", "en": "Hidden layer structure, e.g., '30 30' means 2 layers with 30 neurons each" } }, "batch": { "type": "number", "label": {"zh": "批次大小", "en": "Batch Size"}, "default": 1000, "constraints": {"min": 1, "max": 10000} }, "epoch": { "type": "number", "label": {"zh": "训练轮数", "en": "Training Epochs"}, "default": 100, "constraints": {"min": 1, "max": 10000} }, "lambda_e": { "type": "number", "label": {"zh": "能量损失权重", "en": "Energy Loss Weight"}, "default": 1.0 }, "lambda_f": { "type": "number", "label": {"zh": "力损失权重", "en": "Force Loss Weight"}, "default": 1.0 }, "lambda_v": { "type": "number", "label": {"zh": "维里损失权重", "en": "Virial Loss Weight"}, "default": 0.1 }, "perturb_distance": { "type": "number", "label": {"zh": "微扰距离 (Å)", "en": "Perturbation Distance (Å)"}, "default": 0.1, "tooltip": { "zh": "原子位置随机微扰的最大距离", "en": "Maximum distance for random atomic position perturbation" } }, "doping_element": { "type": "string", "label": {"zh": "掺杂元素", "en": "Doping Element"}, "tooltip": { "zh": "用于替换的掺杂元素符号(如 Fe、Cu)", "en": "Element symbol for doping substitution (e.g., Fe, Cu)" } }, "vacancy_ratio": { "type": "number", "label": {"zh": "空位比例", "en": "Vacancy Ratio"}, "default": 0.05, "constraints": {"min": 0, "max": 0.5}, "tooltip": { "zh": "随机移除原子的比例(0.0-0.5)", "en": "Ratio of atoms to randomly remove (0.0-0.5)" } }, "supercell_size": { "type": "array", "label": {"zh": "超胞尺寸", "en": "Supercell Size"}, "itemType": "number", "length": 3, "default": [2, 2, 2], "tooltip": { "zh": "三个方向的超胞倍数", "en": "Supercell multiplication in three directions" } }, "scale_factor": { "type": "number", "label": {"zh": "晶格缩放因子", "en": "Lattice Scaling Factor"}, "default": 1.0, "constraints": {"min": 0.8, "max": 1.2} }, "strain_tensor": { "type": "array", "label": {"zh": "应变张量", "en": "Strain Tensor"}, "itemType": "number", "length": 6, "default": [0, 0, 0, 0, 0, 0], "tooltip": { "zh": "应变张量的 6 个独立分量:εxx, εyy, εzz, εxy, εxz, εyz", "en": "6 independent components: εxx, εyy, εzz, εxy, εxz, εyz" } }, "slab_miller": { "type": "array", "label": {"zh": "表面Miller指数", "en": "Surface Miller Indices"}, "itemType": "number", "length": 3, "default": [1, 0, 0] }, "slab_layers": { "type": "number", "label": {"zh": "表面层数", "en": "Number of Slab Layers"}, "default": 5, "constraints": {"min": 1, "max": 20} }, "shear_direction": { "type": "array", "label": {"zh": "剪切方向", "en": "Shear Direction"}, "itemType": "number", "length": 3, "default": [1, 0, 0] }, "shear_strain": { "type": "number", "label": {"zh": "剪切应变", "en": "Shear Strain"}, "default": 0.05, "constraints": {"min": 0, "max": 0.3} } }, "components": { "gnep_xyz_import": { "id": "gnep_xyz_import", "name": {"zh": "XYZ轨迹导入", "en": "XYZ Trajectory Import"}, "description": { "zh": "导入分子动力学轨迹或多结构XYZ文件", "en": "Import molecular dynamics trajectories or multi-structure XYZ files" }, "category": "import", "parameters": [] }, "gnep_perturb": { "id": "gnep_perturb", "name": {"zh": "原子位置微扰", "en": "Atomic Perturbation"}, "description": { "zh": "对原子位置施加随机微扰,生成训练数据", "en": "Apply random perturbations to atomic positions for training data generation" }, "category": "data_prep", "parameters": ["perturb_distance"] }, "gnep_doping": { "id": "gnep_doping", "name": {"zh": "原子掺杂", "en": "Atomic Doping"}, "description": { "zh": "随机替换晶格中的原子,模拟掺杂效应", "en": "Randomly substitute atoms in lattice to simulate doping effects" }, "category": "data_prep", "parameters": ["doping_element"] }, "gnep_vacancy": { "id": "gnep_vacancy", "name": {"zh": "随机空位", "en": "Random Vacancies"}, "description": { "zh": "在晶格中随机生成空位缺陷", "en": "Randomly generate vacancy defects in the lattice" }, "category": "data_prep", "parameters": ["vacancy_ratio"] }, "gnep_supercell": { "id": "gnep_supercell", "name": {"zh": "超胞扩展", "en": "Supercell Expansion"}, "description": { "zh": "将晶胞扩展为超胞,增加体系尺寸", "en": "Expand unit cell to supercell to increase system size" }, "category": "data_prep", "parameters": ["supercell_size"] }, "gnep_scaling": { "id": "gnep_scaling", "name": {"zh": "晶格缩放", "en": "Lattice Scaling"}, "description": { "zh": "均匀缩放晶格常数,模拟压力效应", "en": "Uniformly scale lattice constants to simulate pressure effects" }, "category": "data_prep", "parameters": ["scale_factor"] }, "gnep_strain": { "id": "gnep_strain", "name": {"zh": "晶格应变", "en": "Lattice Strain"}, "description": { "zh": "对晶格施加任意应变张量", "en": "Apply arbitrary strain tensor to the lattice" }, "category": "data_prep", "parameters": ["strain_tensor"] }, "gnep_slab": { "id": "gnep_slab", "name": {"zh": "表面切片", "en": "Surface Slab"}, "description": { "zh": "从体相结构生成表面切片模型", "en": "Generate surface slab model from bulk structure" }, "category": "data_prep", "parameters": ["slab_miller", "slab_layers"] }, "gnep_shear": { "id": "gnep_shear", "name": {"zh": "剪切应变", "en": "Shear Strain"}, "description": { "zh": "对结构施加剪切应变,研究力学性质", "en": "Apply shear strain to study mechanical properties" }, "category": "data_prep", "parameters": ["shear_direction", "shear_strain"] }, "gnep_dataset": { "id": "gnep_dataset", "name": {"zh": "数据集创建", "en": "Dataset Creation"}, "description": { "zh": "从处理后的结构生成 GNEP 训练数据集", "en": "Create GNEP training dataset from processed structures" }, "category": "dataset", "parameters": [] }, "gnep_training": { "id": "gnep_training", "name": {"zh": "模型训练", "en": "Model Training"}, "description": { "zh": "训练 GNEP 机器学习势函数模型", "en": "Train GNEP machine learning potential model" }, "category": "training", "parameters": [ "prediction", "cutoff", "n_max", "l_max", "basis_size", "neuron", "batch", "epoch", "lambda_e", "lambda_f", "lambda_v" ] } } } ``` **关键说明**: - **数据预处理组件**: 涵盖微扰、掺杂、空位、超胞、缩放、应变、表面、剪切等工具 - **数据集组件**: 将预处理后的结构转换为 GNEP 训练格式 - **训练组件**: 实际的模型训练节点 - **category**: 用于工作流中的分组和工作目录命名 --- #### Step 2: 实现 GNEP 后端接口(继承 SoftwareInterface) **位置**: `backend/software/gnep/gnep_interface.py` **操作**: 创建完整的 GNEP 接口类,继承 `SoftwareInterface` 并实现所有抽象方法 ```python """ GNEP Interface Module - Machine Learning Potential Training 继承自 SoftwareInterface 基类,实现 GNEP 专用功能 """ import os import json import logging from typing import Dict, List, Optional, Any from pathlib import Path from ..base_interface import SoftwareInterface from ..registry import SoftwareRegistry from ..decorators import log_execution, validate_calculation_type logger = logging.getLogger(__name__) @SoftwareRegistry.register('gnep') class GNEPInterface(SoftwareInterface): """GNEP 机器学习势函数训练接口""" SOFTWARE_NAME = "gnep" SUPPORTED_CALCULATION_TYPES = [ 'import', 'data_prep', 'dataset', 'training' ] def __init__(self, gnep_executable: str = "gnep", **kwargs): super().__init__(**kwargs) self.gnep_executable = gnep_executable # ========== 必须实现的 5 个抽象方法 ========== @log_execution @validate_calculation_type def generate_inputs(self, manager, node: Dict[str, Any], structure_info: Optional[Dict[str, Any]] = None, remote_calc_path: str = None, job_params: Optional[Dict[str, Any]] = None, copy_instructions: Optional[List[str]] = None, precomputed_files: Optional[Dict[str, str]] = None, shared_files: Optional[Dict[str, str]] = None) -> None: """生成 GNEP 输入文件""" config = node.get('data', {}).get('config', {}) calculation_type = self.get_calculation_type(node) final_params = {**config, **(job_params or {})} # 创建远程目录 manager.execute_command(f"mkdir -p {remote_calc_path}") # 根据计算类型生成不同的输入 if calculation_type == 'training': # 生成 gnep.in 配置文件 gnep_content = self._format_gnep_input(final_params) gnep_path = f"{remote_calc_path}/gnep.in" self.upload_if_changed(manager, gnep_content, gnep_path) # 处理训练数据文件(从 precomputed_files 获取) if precomputed_files and 'train.xyz' in precomputed_files: self.upload_if_changed( manager, precomputed_files['train.xyz'], f"{remote_calc_path}/train.xyz" ) if precomputed_files and 'test.xyz' in precomputed_files: self.upload_if_changed( manager, precomputed_files['test.xyz'], f"{remote_calc_path}/test.xyz" ) elif calculation_type == 'data_prep': # 数据预处理节点生成 Python 脚本 script_content = self._generate_data_prep_script(node, final_params) script_path = f"{remote_calc_path}/data_prep.py" self.upload_if_changed(manager, script_content, script_path) # 如果有输入结构,上传 if structure_info: from ase.io import write structure_path = f"{remote_calc_path}/input.xyz" # 这里需要根据实际情况转换 structure_info 到 ASE Atoms 对象 # write(structure_path, atoms_object) elif calculation_type == 'dataset': # 数据集创建节点,收集所有预处理后的结构 dataset_script = self._generate_dataset_script(node, final_params) script_path = f"{remote_calc_path}/create_dataset.py" self.upload_if_changed(manager, dataset_script, script_path) # 生成提交脚本 submit_script = self._format_submission_script(final_params, copy_instructions) submit_path = f"{remote_calc_path}/submit.pbs" self.upload_if_changed(manager, submit_script, submit_path) logger.info(f"✓ Generated GNEP inputs for {calculation_type} at {remote_calc_path}") # 核心方法2: 计算类型识别 def get_calculation_type(self, node: Dict) -> Optional[str]: """从节点配置中提取计算类型(目录名)""" node_data = node.get('data', {}) node_type = node_data.get('nodeType', '') # 节点类型 -> 计算类型映射 type_mapping = { 'gnep_xyz_import': 'import', 'gnep_perturb': 'data_prep', 'gnep_doping': 'data_prep', 'gnep_vacancy': 'data_prep', 'gnep_supercell': 'data_prep', 'gnep_scaling': 'data_prep', 'gnep_strain': 'data_prep', 'gnep_slab': 'data_prep', 'gnep_shear': 'data_prep', 'gnep_dataset': 'dataset', 'gnep_training': 'training' } calc_type = type_mapping.get(node_type) if calc_type: logger.debug(f"Node type '{node_type}' mapped to calculation type '{calc_type}'") return calc_type else: logger.warning(f"Unknown GNEP node type: {node_type}") return None # 核心方法3: 结果解析 @log_execution @cache_result(ttl_seconds=120) def parse_results(self, manager, remote_calc_path: str, calculation_type: str) -> Dict[str, Any]: """解析GNEP计算结果""" results = {'available_files': []} # 列出可用文件 ls_result = manager.execute_command(f"ls -F {remote_calc_path}") if ls_result['success']: all_files = ls_result['output'].split() results['available_files'] = all_files try: if calculation_type == 'training': # 解析训练结果 log_file = f"{remote_calc_path}/training.log" log_result = manager.read_file_content(log_file) if log_result['success']: training_metrics = self._parse_training_log(log_result['content']) results.update(training_metrics) # 检查模型文件是否存在 model_file = f"{remote_calc_path}/nep.txt" model_exists = manager.execute_command(f"test -f {model_file}") results['model_trained'] = model_exists['success'] elif calculation_type == 'data_prep': # 检查输出结构数量 output_dir = f"{remote_calc_path}/output_structures" count_cmd = f"ls {output_dir}/*.xyz 2>/dev/null | wc -l" count_result = manager.execute_command(count_cmd) if count_result['success']: results['structures_generated'] = int(count_result['output'].strip()) elif calculation_type == 'dataset': # 检查训练集和测试集 for dataset in ['train.xyz', 'test.xyz']: dataset_path = f"{remote_calc_path}/{dataset}" if dataset in all_files: # 统计结构数量 count_cmd = f"grep -c 'Lattice' {dataset_path}" count_result = manager.execute_command(count_cmd) if count_result['success']: results[f'{dataset}_structures'] = int(count_result['output'].strip()) except Exception as e: logger.error(f"Error parsing GNEP results from {remote_calc_path}: {str(e)}") return results # 核心方法4: 文件依赖管理 def get_file_dependencies(self, calculation_type: str) -> List[Dict[str, str]]: """定义不同节点之间的文件依赖关系""" calc_dependencies = { 'data_prep': [ {'source': 'output_structures/', 'dest': 'input_structures/', 'type': 'copy'} ], 'dataset': [ {'source': 'output_structures/', 'dest': 'structures/', 'type': 'copy'} ], 'training': [ {'source': 'train.xyz', 'dest': 'train.xyz', 'type': 'link'}, {'source': 'test.xyz', 'dest': 'test.xyz', 'type': 'link'} ] } return calc_dependencies.get(calculation_type, []) # 核心方法5: 收敛验证(可选实现) def verify_convergence(self, manager, remote_calc_path: str, calculation_type: str) -> bool: """验证GNEP训练是否收敛""" if calculation_type != 'training': return True # 非训练任务默认通过 # 检查训练日志中的最终损失 log_file = f"{remote_calc_path}/training.log" log_result = manager.read_file_content(log_file) if not log_result['success']: return False # 解析最终损失值 lines = log_result['content'].strip().split('\n') for line in reversed(lines): if 'Final loss' in line or 'RMSE' in line: try: loss_value = float(line.split()[-1]) # 损失低于阈值视为收敛 return loss_value < 0.01 except (ValueError, IndexError): continue return False # 辅助方法: 生成GNEP输入文件 def _format_gnep_input(self, params: Dict[str, Any]) -> str: """生成gnep.in输入文件""" lines = [ f"prediction {1 if params.get('prediction', False) else 0}", f"cutoff {' '.join(map(str, params.get('cutoff', [4.0, 4.5])))}", f"n_max {' '.join(map(str, params.get('n_max', [8, 8])))}", f"l_max {params.get('l_max', 4)}", f"basis_size {' '.join(map(str, params.get('basis_size', [15, 15])))}", f"neuron {params.get('neuron', '30')}", f"batch {params.get('batch', 1000)}", f"epoch {params.get('epoch', 100)}", f"lambda_e {params.get('lambda_e', 1.0)}", f"lambda_f {params.get('lambda_f', 1.0)}", f"lambda_v {params.get('lambda_v', 0.1)}" ] return '\n'.join(lines) # 辅助方法: 生成数据预处理脚本 def _generate_data_prep_script(self, node: Dict, params: Dict[str, Any]) -> str: """生成数据预处理Python脚本""" node_type = node.get('data', {}).get('nodeType', '') # 根据不同的数据预处理类型生成脚本 if 'perturb' in node_type: distance = params.get('perturb_distance', 0.1) return f""" from ase.io import read, write import numpy as np atoms = read('input.xyz') for i, atom in enumerate(atoms): displacement = np.random.uniform(-{distance}, {distance}, 3) atoms.positions[i] += displacement write('output_structures/perturbed.xyz', atoms) """ elif 'supercell' in node_type: size = params.get('supercell_size', [2, 2, 2]) return f""" from ase.io import read, write atoms = read('input.xyz') supercell = atoms * {size} write('output_structures/supercell.xyz', supercell) """ # ... 其他数据预处理类型的脚本生成逻辑 return "# Data preparation script\n" # 辅助方法: 生成数据集创建脚本 def _generate_dataset_script(self, node: Dict, params: Dict[str, Any]) -> str: """生成数据集创建脚本""" return """ from ase.io import read, write import os # 收集所有结构文件 structures = [] for root, dirs, files in os.walk('structures/'): for file in files: if file.endswith('.xyz'): atoms = read(os.path.join(root, file)) structures.append(atoms) # 分割训练集和测试集 (80/20) split_idx = int(len(structures) * 0.8) train_structures = structures[:split_idx] test_structures = structures[split_idx:] # 写入数据集文件 write('train.xyz', train_structures) write('test.xyz', test_structures) print(f'Train: {len(train_structures)}, Test: {len(test_structures)}') """ # 辅助方法: 生成作业脚本 def _format_submission_script(self, params: Dict[str, Any], copy_instructions=None) -> str: """生成PBS作业脚本""" job_name = params.get('job_name', 'alkemie_gnep') nodes = params.get('nodes', 1) ppn = params.get('ppn', 32) walltime = params.get('walltime', '24:00:00') queue = params.get('queue', 'normal') # 从software_config.json加载环境变量 software_config_path = os.path.join(os.path.dirname(__file__), '..', '..', 'software_config.json') env_setup = "" if os.path.exists(software_config_path): with open(software_config_path, 'r') as f: softwares = json.load(f) software_config = next((s for s in softwares if s['name'].lower() == 'gnep'), None) if software_config and software_config.get('environment_setup'): env_setup = software_config['environment_setup'] script_lines = [ "#!/bin/bash", f"#PBS -N {job_name}", f"#PBS -l nodes={nodes}:ppn={ppn}", f"#PBS -l walltime={walltime}", f"#PBS -q {queue}", "#PBS -V", "", "cd $PBS_O_WORKDIR", "", "# 加载GNEP环境", env_setup, "" ] if copy_instructions: script_lines.append("# 复制依赖文件") if isinstance(copy_instructions, list): script_lines.extend(copy_instructions) script_lines.append("") script_lines.extend([ "# 执行GNEP计算", "echo '=== Starting GNEP calculation ==='", f"gnep > training.log 2>&1", "", "echo '=== Calculation finished ==='", "exit 0" ]) return "\n".join(script_lines) # 辅助方法: 解析训练日志 def _parse_training_log(self, log_content: str) -> Dict[str, Any]: """解析GNEP训练日志""" results = {} lines = log_content.strip().split('\n') # 提取训练损失曲线 losses = [] for line in lines: if 'Epoch' in line and 'Loss' in line: try: parts = line.split() epoch = int(parts[1]) loss = float(parts[3]) losses.append({'epoch': epoch, 'loss': loss}) except (ValueError, IndexError): continue if losses: results['training_curve'] = losses results['final_loss'] = losses[-1]['loss'] results['converged'] = losses[-1]['loss'] < 0.01 return results ``` #### Step 3: 注册软件接口到主应用 > **说明**: GNEP 接口已使用 `@SoftwareRegistry.register('gnep')` 装饰器注册,**无需在 `app.py` 中手动添加**。只需确保 GNEP 模块被导入即可。 **位置**: `backend/software/__init__.py` **添加导入**: ```python # 在文件顶部添加导入 from .gnep.gnep_interface import GNEPInterface # 添加到导出列表 __all__ = [ 'SoftwareInterface', 'SoftwareRegistry', 'VASPInterface', 'GNEPInterface', # ⭐ 添加GNEP接口 ] ``` **验证注册**: ```python # 在 backend/ 目录下运行 Python 交互式环境 >>> from software import SoftwareRegistry >>> SoftwareRegistry.list_all() ['vasp', 'gnep'] # GNEP已自动注册 >>> gnep_interface = SoftwareRegistry.get_info('gnep') ``` **说明**: 由于使用了 `@SoftwareRegistry.register()` 装饰器,软件接口在模块导入时自动注册到全局注册表,`app.py` 和 `remote_workflow_manager.py` 会自动从注册表获取接口实例,**无需手动修改这些文件**。 #### Step 4: 创建前端 UI 配置(Schema-Driven) > **⭐ 架构原则**: 前端**只定义 UI 配置**(color, icon, categoryName, defaultConfig),组件元数据(id, name, description, parameters)从后端 Schema API 自动获取。 **位置**: `src/components/software/gnep/GnepComponents.js` **操作**: 定义 GNEP_UI_CONFIG 并使用 `useComponents` Hook 动态加载 **代码实现**: ```javascript /** * GNEP UI Configuration - Frontend-Specific Settings Only * * 架构说明: * - Backend: shared/schemas/gnep_parameters.json 定义组件元数据 * - Frontend: 此文件定义 UI 配置(color, icon, categoryName) * - Runtime: useComponents hook 合并两者 */ import { useComponents } from '../../../hooks/useComponents'; /** * GNEP UI 配置 - 仅定义可视化属性 */ export const GNEP_UI_CONFIG = { // ========== 数据导入组件 ========== gnep_xyz_import: { color: '#13c2c2', icon: '📥', categoryName: '数据导入', defaultConfig: {} // 导入节点无需默认参数 }, // ========== 数据预处理工具 ========== gnep_perturb: { color: '#fa8c16', icon: '🌀', categoryName: '数据预处理', defaultConfig: { perturb_distance: 0.1 } }, gnep_doping: { color: '#fa541c', icon: '🔄', categoryName: '数据预处理', defaultConfig: { doping_element: '' } }, gnep_vacancy: { color: '#f5222d', icon: '⭕', categoryName: '数据预处理', defaultConfig: { vacancy_ratio: 0.05 } }, gnep_supercell: { color: '#eb2f96', icon: '📦', categoryName: '数据预处理', defaultConfig: { supercell_size: [2, 2, 2] } }, gnep_scaling: { color: '#722ed1', icon: '📏', categoryName: '数据预处理', defaultConfig: { scale_factor: 1.0 } }, gnep_strain: { color: '#1890ff', icon: '🔶', categoryName: '数据预处理', defaultConfig: { strain_tensor: [0, 0, 0, 0, 0, 0] } }, gnep_slab: { color: '#52c41a', icon: '🔷', categoryName: '数据预处理', defaultConfig: { slab_miller: [1, 0, 0], slab_layers: 5 } }, gnep_shear: { color: '#faad14', icon: '🔀', categoryName: '数据预处理', defaultConfig: { shear_direction: [1, 0, 0], shear_strain: 0.05 } }, // ========== 数据集创建 ========== gnep_dataset: { color: '#2f54eb', icon: '📊', categoryName: '数据集', defaultConfig: {} }, // ========== 模型训练 ========== gnep_training: { color: '#52c41a', icon: '🧠', categoryName: '模型训练', defaultConfig: { prediction: false, cutoff: [4.0, 4.5], n_max: [8, 8], l_max: 4, basis_size: [15, 15], neuron: '30', batch: 1000, epoch: 100, lambda_e: 1.0, lambda_f: 1.0, lambda_v: 0.1 } } }; /** * Hook to get GNEP components with merged backend data and UI config * * 此 Hook 会: * 1. 从后端 GET /api/software/gnep/components 获取元数据 * 2. 与 GNEP_UI_CONFIG 合并 * 3. 返回完整的组件定义 * * Usage: * const { components, loading, error } = useGnepComponents(); * * @returns {Object} { components, loading, error, refetch } */ export const useGnepComponents = (options = {}) => { return useComponents('gnep', GNEP_UI_CONFIG, options); }; ``` **关键说明**: - **color/icon/categoryName**: 前端 UI 专属配置 - **defaultConfig**: 可选的 UI 级默认值,作为 Schema 默认值的补充 - **useGnepComponents()**: 动态加载 Hook,自动从后端获取组件元数据并合并 --- #### Step 5: 创建配置表单渲染器(Schema-Driven) > **⭐ 重要**: 使用 `SchemaForm` 组件自动渲染,参数定义完全来自后端 Schema,无需手动编写表单项。 **位置**: `src/components/software/gnep/GnepConfigForm.js` **操作**: 创建表单渲染器,使用 `useSchema` Hook 和 `SchemaForm` 组件 **代码实现**: ```javascript /** * GNEP Configuration Form Renderer (Schema-Driven) * * 此文件使用 Schema-Driven 架构,参数定义来自 shared/schemas/gnep_parameters.json * * Migration from Static Definitions: * - 移除 600+ 行硬编码的 labelMap, tooltip, 表单逻辑 * - 使用 SchemaForm 组件自动渲染 * - 添加新参数只需更新 JSON Schema */ import React from 'react'; import { Spin, Alert } from 'antd'; import { SchemaForm } from '../../common/SchemaForm'; import { useSchema } from '../../../hooks/useSchema'; /** * GNEP 配置表单组件 (Schema-Driven) * * @param {Object} config - 节点配置对象 * @param {Object} form - Ant Design Form 实例 * @param {string} nodeType - 节点类型(组件 ID) * @param {string} software - 软件名称(应为 'gnep') * @param {Object} node - 完整节点对象 * @param {Array} nodes - 所有节点(保留用于 API 兼容性) * @param {Array} edges - 所有边(保留用于 API 兼容性) * @param {Function} updateNodeData - 更新节点数据回调(保留用于 API 兼容性) * @returns {Array} 表单项数组(用于向后兼容) */ export const renderGnepConfigForm = (config, form, nodeType, software, node, nodes, edges, updateNodeData) => { // 输入验证 if (!nodeType) { return [ ]; } if (software !== 'gnep') { return [ ]; } // 返回数组用于向后兼容 return [ ]; }; /** * 内部组件:获取 Schema 并渲染表单 */ const GnepSchemaForm = ({ nodeType, form, initialValues }) => { const { schema, loading, error } = useSchema('gnep', nodeType); // 显示加载状态 if (loading) { return (
); } // 显示错误提示 if (error) { return ( ); } // 渲染 Schema-Driven 表单 return ( ); }; /** * 默认导出用于向后兼容 */ export default renderGnepConfigForm; ``` **关键亮点**: - **useSchema('gnep', nodeType)**: 从后端 GET `/api/software/gnep/parameters` 获取参数定义 - **SchemaForm**: 通用表单组件,根据 Schema 自动渲染: - 数组参数:自动展开为多个 InputNumber - 数值参数:自动应用 min/max/step 约束 - 选项参数:自动渲染为 Select 下拉框 - Tooltip:自动从 Schema 中提取并显示 - **无需手动维护**: 添加新参数只需更新 `shared/schemas/gnep_parameters.json` --- #### Step 6: 注册到软件中心和组件库 **位置**: `src/components/software/softwareConfig.js` **操作**: 注册 GNEP 到动态组件加载系统 **Step 6.1: 添加导入**: ```javascript // 导入 GNEP UI 配置(注意:不再导入静态组件数组) import { useGnepComponents, GNEP_UI_CONFIG } from './gnep/GnepComponents'; import { renderGnepConfigForm } from './gnep/GnepConfigForm'; ``` **Step 6.2: 更新 useAllComponents Hook**: ```javascript /** * Hook to dynamically load all components from backend APIs * Merges backend component definitions with frontend UI configurations * * @returns {Object} { components, loading, error } */ export const useAllComponents = () => { // ⭐ 动态加载 VASP 和 GNEP 组件 const vaspResult = useComponents('vasp', VASP_UI_CONFIG, { enabled: true }); const gnepResult = useComponents('gnep', GNEP_UI_CONFIG, { enabled: true }); // 其他软件仍使用静态定义(TODO: 未来迁移到 Schema-Driven) const staticComponents = [ ...QE_COMPONENTS, ...LAMMPS_COMPONENTS, ...HIGH_THROUGHPUT_COMPONENTS ]; return { components: [ ...(vaspResult.components || []), ...(gnepResult.components || []), // ⭐ 添加 GNEP 动态组件 ...staticComponents ], loading: vaspResult.loading || gnepResult.loading, error: vaspResult.error || gnepResult.error }; }; ``` **Step 6.3: 注册表单渲染器**: ```javascript /** * 软件配置表单渲染器注册表 */ export const SOFTWARE_CONFIG_RENDERERS = { vasp: renderVaspConfigForm, gnep: renderGnepConfigForm, // ⭐ 注册 GNEP 表单渲染器 'high-throughput': renderHighThroughputConfigForm, }; ``` **Step 6.4: 软件中心配置**(位置:`src/components/sidebar/SoftwareCenter.js`): ```javascript // 软件 Logo 配置 const SOFTWARE_LOGOS = { 'VASP': '/logo/VASP_logo-on-white.svg', 'GNEP': '🧠' // ⭐ 添加 GNEP Logo(机器学习势图标) }; // 软件类别定义 export const SOFTWARE_CATEGORIES = { 'DFT': { id: 'dft', name: 'DFT计算软件', nameEn: 'DFT Software', description: '第一性原理密度泛函理论计算软件', descriptionEn: 'First-principles Density Functional Theory calculation software', color: '#1890ff', icon: '⚛️' }, 'ML_POTENTIAL': { // ⭐ 新增机器学习势类别 id: 'ml_potential', name: '机器学习势函数', nameEn: 'Machine Learning Potentials', description: '基于深度学习的原子间势函数训练与预测', descriptionEn: 'Deep learning-based interatomic potential training and prediction', color: '#52c41a', icon: '🧠' } }; // 软件详细信息 const SOFTWARE_INFO = { 'VASP': { name: 'VASP', fullName: 'Vienna Ab-initio Simulation Package', description: '基于密度泛函理论的第一性原理计算软件包,广泛用于固体物理和材料科学研究。', category: 'DFT', subcategory: '平面波基组', website: 'https://www.vasp.at/', defaultEnv: 'module load vasp/6.3.0\nexport VASP_PP_PATH=/path/to/potcar', features: ['DFT计算', '结构优化', '电子结构分析', '能带计算'] }, 'GNEP': { // ⭐ 添加 GNEP 软件信息 name: 'GNEP', fullName: 'Gradient-optimized Neuroevolution Potential', description: '机器学习势函数训练框架,支持数据预处理、模型训练和分子动力学模拟。', category: 'ML_POTENTIAL', subcategory: '神经网络势', website: 'https://gnep-ml-potential.org', // 替换为实际网址 defaultEnv: 'module load python/3.9\nexport GNEP_PATH=/path/to/gnep', features: [ '数据预处理工具(微扰、掺杂、空位、超胞等)', '神经网络势训练', '高效分子动力学模拟', '支持多体相互作用' ] } }; ``` **Step 6.5: 测试集成** **验证步骤**: 1. **重启开发服务器**: `npm run dev` 2. **检查组件加载**: - 打开浏览器控制台 - 查看 `GET /api/software/gnep/components` 请求是否成功 - 验证返回的组件数量(应为 11 个) 3. **测试工作流编辑器**: - 在左侧组件库中找到 "机器学习势函数" 分类 - 验证 11 个 GNEP 组件是否正确显示(带图标和颜色) 4. **测试参数表单**: - 拖拽 "模型训练" 组件到画布 - 打开配置面板,验证所有参数是否正确渲染 - 特别检查数组参数(cutoff, n_max)是否展开为多个输入框 5. **测试作业提交**: 配置完整工作流并提交测试作业 6. **验证结果解析**: 检查训练完成后的结果展示 --- `★ Insight ─────────────────────────────────────` **Schema-Driven 架构的核心优势**: 1. **前后端分离**:前端只管 UI,后端管数据 2. **自动同步**:修改 Schema 后,前端表单自动更新 3. **快速开发**:添加 11 个组件只需 ~100 行前端代码(传统方式需 500+ 行) 4. **类型安全**:Schema 提供参数校验和约束 `─────────────────────────────────────────────────` --- ## 调试方式 ### Python后端 python代码中添加 ```python logger.debug("=== vasprun.xml DOS Parsing Debug ===") # or logger.info("Attempting to parse DOSCAR as fallback") # or logger.warning(f"Error parsing DOSCAR line {i+start_line+1}: {e}") # or logger.error("No valid DOS data parsed from DOSCAR") ``` 后端将在终端输出 ```bash INFO:software.vasp.vasp_interface:Attempting to parse DOSCAR as fallback ``` #### vasp_interface.py专用调试函数 ```python def _dump_raw_content(self, filename: str, content: str) -> None: """ Dump raw content to a file for debugging purposes. """ try: debug_dir = os.path.join(os.path.dirname(__file__), 'debug_output') os.makedirs(debug_dir, exist_ok=True) filepath = os.path.join(debug_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: f.write(content) logger.info(f"Raw content dumped to: {filepath}") except Exception as e: logger.error(f"Failed to dump raw content: {e}") ``` 在需要调试的地方调用该函数 ```python self._dump_raw_content("DOSCAR_failed_parse.txt", content) ``` #### 示例 ### JavaScript前端 ```javascript console.log('结果数据详情:', result.results); // or console.error('加载项目失败:', error); // or message.warning("工作流已锁定,无法删除节点"); message.error('删除连接失败 - Delete connection failed: ' + error.message); message.success('连接已删除 - Connection deleted successfully'); ``` 浏览器`F12`-->`控制台`查看输出结果 ## 🔧 API接口详细说明 ### 🖥️ 服务器信息 #### `GET /api/server/health` **功能**: 健康检查端点 **返回**: `{"status": "ok", "timestamp": "...", "version": "2.1.0"}` #### `GET /api/health` **功能**: 详细健康检查 **返回**: ```json { "status": "healthy", "timestamp": "2025-01-07T12:00:00", "supported_formats": ["vasp", "poscar", "contcar", "cif", "xyz", "pdb", "json", "traj"], "message": "Alkemie Backend API is running" } ``` #### `GET /api/server/info` **功能**: 获取服务器环境信息 **返回**: ```json { "success": true, "user": "username", "home": "/home/username", "hostname": "hpc-login-node", "python_version": "3.10.12", "working_directory": "/home/username/alkemie-workspaces/alkemie-server", "base_directory": "/home/username/alkemie-workspaces/alkemie-projects", "queue_system": "PBS", "connected_clients": 3, "monitor_status": {...} } ``` --- ### 📁 项目管理 #### `GET /api/projects` **功能**: 列出所有项目 **缓存**: 60秒 TTL **返回**: ```json { "success": true, "projects": [ { "id": "project_20250107_120000", "name": "Si Band Structure", "description": "Silicon band structure calculation", "status": "completed", "created": "2025-01-07T12:00:00", "modified": "2025-01-07T13:30:00", "type": "local" } ] } ``` #### `POST /api/projects` **功能**: 创建新项目 **请求体**: ```json { "name": "New Project", "description": "Project description" } ``` **返回**: ```json { "success": true, "project_id": "project_20250107_120000", "project_path": "/home/username/alkemie-workspaces/alkemie-projects/project_20250107_120000" } ``` #### `GET /api/projects/` **功能**: 获取项目详情 **返回**: 包含nodes、edges、structure_info等完整项目数据 #### `PUT /api/projects/` **功能**: 保存项目工作流并生成输入文件 **请求体**: ```json { "nodes": [...], "edges": [...], "structure_info": {...} } ``` **副作用**: - 生成各节点的输入文件(INCAR, POSCAR, KPOINTS等) - 更新 project_info.json - 通过 WebSocket 广播项目状态更新 #### `DELETE /api/projects/` **功能**: 删除项目及所有文件 **副作用**: 删除整个项目目录,不可恢复 --- ### ⚡ 工作流执行 #### `POST /api/workflows/execute` **功能**: 提交工作流到队列系统 **请求体**: ```json { "project_id": "project_20250107_120000", "job_params": { "nodes": 2, "ppn": 32, "walltime": "24:00:00", "queue": "normal" } } ``` **返回**: ```json { "success": true, "job_id": "12345.pbs-server", "message": "Workflow submitted successfully" } ``` **实时通信**: 通过 WebSocket 推送作业状态更新 #### `GET /api/projects//status` **功能**: 获取项目当前状态 **返回**: ```json { "success": true, "status": "running", "node_statuses": { "node-1": {"status": "completed", "job_id": "12345"}, "node-2": {"status": "running", "job_id": "12346"} } } ``` #### `GET /api/projects//nodes//results` **功能**: 获取节点计算结果 **返回**: ```json { "success": true, "results": { "energy": -10.8745, "converged": true, "dos_data": {"tdos": [...]}, "band_data": {...}, "available_files": ["OUTCAR", "vasprun.xml", "DOSCAR"] } } ``` --- ### 📊 队列和系统信息 #### `GET /api/queue-info?refresh=false` **功能**: 获取HPC队列系统信息 **参数**: - `refresh=true`: 强制刷新缓存 **缓存**: 30秒 TTL + 后台软刷新 **实时通信**: 后台监控自动通过 WebSocket 推送更新 #### `GET /api/system-performance?refresh=false` **功能**: 获取系统性能指标 **缓存**: 10秒 TTL + 后台软刷新 **返回**: CPU、内存、磁盘使用率等 --- ### 📤 文件操作 #### `POST /api/upload` **功能**: 上传结构文件到服务器 **请求**: `multipart/form-data` - `file`: 文件对象 - `project_id`: 项目ID(可选) **返回**: ```json { "success": true, "filename": "structure.cif", "file_path": "/path/to/file", "structure_info": { "lattice": [...], "atoms": [...], "formula": "Si2" } } ``` **实时通信**: 通过 WebSocket 推送上传进度 #### `POST /api/parse` **功能**: 解析结构文件 **请求**: `multipart/form-data` **返回**: 结构信息(晶格、原子坐标、化学式等) #### `GET /api/download/?project_id=xxx` **功能**: 从服务器下载文件 **参数**: - `project_id`: 从项目目录下载(可选) **返回**: 文件数据流 --- ### ⚛️ POTCAR 管理 #### `POST /api/potcar/upload` **功能**: 上传POTCAR文件到库 **请求**: `multipart/form-data` **返回**: ```json { "success": true, "element": "Si", "path": "/path/to/potcar_library/Si/POTCAR" } ``` #### `GET /api/potcar/check` **功能**: 列出所有可用的POTCAR元素 **返回**: ```json { "success": true, "Si": "ready", "Fe": "ready", "O": "ready" } ``` #### `POST /api/potcar/check` **功能**: 检查特定元素的POTCAR是否存在 **请求体**: ```json { "elements": ["Si", "Fe", "O"] } ``` **返回**: ```json { "success": true, "Si": "ready", "Fe": "missing", "O": "ready" } ``` --- ### 🔧 软件中心 #### `GET /api/software` **功能**: 列出所有软件配置 **返回**: 软件配置数组 #### `POST /api/software` **功能**: 添加新软件配置 **请求体**: ```json { "name": "VASP", "version": "6.4.0", "environment_setup": "module load vasp/6.4.0", "enabled": true, "configured": true } ``` #### `PUT /api/software/` **功能**: 更新软件配置 #### `DELETE /api/software/` **功能**: 删除软件配置 --- ### ⚙️ 设置管理 #### `GET /api/settings/ports` **功能**: 获取端口设置 **返回**: ```json { "success": true, "settings": {"port": 8443} } ``` #### `POST /api/settings/ports` **功能**: 更新端口设置(需要重启) **请求体**: ```json { "port": 9000 } ``` --- ### 🌐 WebSocket 实时事件 Alkemie 使用 Socket.IO 进行实时双向通信。 #### 连接 ```javascript // 前端连接 import io from 'socket.io-client'; const socket = io('https://localhost:8443'); ``` #### 订阅事件 ```javascript // 订阅项目更新 socket.emit('subscribe_project', {project_id: 'project_xxx'}); // 监听作业状态变化 socket.on('job_status_changed', (data) => { console.log('Job status:', data.status); }); // 监听项目状态变化 socket.on('project_status_changed', (data) => { console.log('Project status:', data.status); }); // 监听节点状态变化 socket.on('node_status_changed', (data) => { console.log('Node:', data.node_id, 'Status:', data.status); }); // 监听队列信息更新 socket.emit('subscribe_queue'); socket.on('queue_info_updated', (data) => { console.log('Queue info:', data); }); // 监听系统性能更新 socket.emit('subscribe_system'); socket.on('system_metrics_updated', (data) => { console.log('System metrics:', data); }); ``` #### 可用事件 | 事件名称 | 方向 | 说明 | |---------|------|------| | `connect` | 服务器→客户端 | 连接成功 | | `disconnect` | 服务器→客户端 | 连接断开 | | `subscribe_project` | 客户端→服务器 | 订阅项目更新 | | `unsubscribe_project` | 客户端→服务器 | 取消订阅项目 | | `subscribe_queue` | 客户端→服务器 | 订阅队列信息 | | `subscribe_system` | 客户端→服务器 | 订阅系统性能 | | `job_status_changed` | 服务器→客户端 | 作业状态变化 | | `project_status_changed` | 服务器→客户端 | 项目状态变化 | | `node_status_changed` | 服务器→客户端 | 节点状态变化 | | `queue_info_updated` | 服务器→客户端 | 队列信息更新 | | `system_metrics_updated` | 服务器→客户端 | 系统性能更新 | | `file_upload_progress` | 服务器→客户端 | 文件上传进度 | | `error` | 服务器→客户端 | 错误通知 | | `notification` | 服务器→客户端 | 通用通知 | --- ### 🔍 测试 API 端点 ```bash # 健康检查 curl -k https://localhost:8443/api/server/health # 获取服务器信息 curl -k https://localhost:8443/api/server/info # 列出所有项目 curl -k https://localhost:8443/api/projects # 创建新项目 curl -k -X POST https://localhost:8443/api/projects \ -H "Content-Type: application/json" \ -d '{"name":"Test Project","description":"A test project"}' # 上传结构文件 curl -k -X POST https://localhost:8443/api/upload \ -F "file=@POSCAR" \ -F "project_id=project_20250107_120000" # 上传 POTCAR curl -k -X POST https://localhost:8443/api/potcar/upload \ -F "file=@POTCAR" # 检查特定元素的 POTCAR curl -k -X POST https://localhost:8443/api/potcar/check \ -H "Content-Type: application/json" \ -d '{"elements":["Si","Fe","O"]}' # 提交工作流 curl -k -X POST https://localhost:8443/api/workflows/execute \ -H "Content-Type: application/json" \ -d '{"project_id":"project_20250107_120000","job_params":{"nodes":2,"ppn":32}}' # 获取项目状态 curl -k https://localhost:8443/api/projects/project_20250107_120000/status # 强制刷新队列信息 curl -k "https://localhost:8443/api/queue-info?refresh=true" # 下载文件 curl -k -O "https://localhost:8443/api/download/POSCAR?project_id=project_20250107_120000" ``` --- ### 📋 API 设计原则 1. **RESTful 设计**: 遵循 HTTP 方法语义(GET、POST、PUT、DELETE) 2. **JSON 响应**: 所有 API 返回统一的 JSON 格式 3. **错误处理**: 使用适当的 HTTP 状态码(200、400、404、500) 4. **缓存策略**: 高频请求使用 TTL 缓存 + 后台软刷新 5. **实时推送**: 状态变化通过 WebSocket 主动推送,无需轮询 6. **幂等性**: PUT/DELETE 操作具有幂等性 7. **安全性**: HTTPS + CORS 配置 + 输入验证