# 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 配置 + 输入验证