从终端图像到 Slurm 和 VASP:我们最近做的三个工具
本文由 AI 生成。
最近我们把几个原本只服务于自己工作流的小工具,整理成了可以交给新用户使用的版本:一个终端图像与结构预览工具链 icat/ovicat,一个 Slurm 集群 TUI turm,以及一个 VASP 输入文件 LSP vasp-lsp。
它们解决的是三种不同的摩擦:在 SSH 终端里看图、在集群上管理作业、在编辑 INCAR 时尽早发现错误。共同点是,它们都不是为了“再做一个大而全的平台”,而是把已经存在的工具接到日常科研工作最顺手的位置上。
本文会重点说明:上游工具是什么,我们的版本到底改了什么,新用户需要准备什么,以及哪些场景下不应该使用它们。
先看结论
| 工具 | 解决的问题 | 我们的版本与上游/相似工具的区别 |
|---|---|---|
icat/ovicat |
在终端内看图片、结构和轨迹 | icat 直接发送 Kitty graphics protocol;ovicat 把结构文件交给 OVITO 渲染后再交给 icat,形成一条命令的预览链 |
turm |
查看、筛选、取消 Slurm 作业 | 基于 karimknaebel/turm 的个人 fork,加入进入作业目录、节点监控、复制 Job ID、可拖动面板等集群工作流功能 |
vasp-lsp |
编辑 INCAR/POSCAR/KPOINTS 时获得补全和诊断 | 基于 newtontech/VASP-LSP 的 fork,补充官方 Wiki 标签目录、布尔值验证和 Neovim 编辑同步修复,并提供可直接复制的配置 |
下面的工具源码和安装说明都放在这个博客仓库里:tools/icat/。
一、icat:它不是“传文件”,而是把图像画进终端
1. icat 和 scp、rsync 有什么不同?
先把一个容易混淆的概念分开:
scp和rsync的任务是传输或同步文件。它们把字节放到另一台机器的文件系统里。kitty +kitten icat的任务是在 Kitty 终端中显示图片。- 我们的
icat也是显示器,不是文件传输器。它读取本地文件,用 Pillow 做必要的解码和缩放,然后发出终端能理解的图形控制序列。
所以,下面两件事完全不同:
# 把远端图片复制到本地
scp user@cluster:/path/to/figure.png .
# 在当前终端直接显示远端机器上的图片
ssh user@cluster icat /path/to/figure.png
第二条命令并没有把图片保存到本地磁盘。远端 icat 把图片编码成 Kitty graphics protocol 的数据,经 SSH 的标准输出传回终端,终端负责把它画出来。这也是它适合看 VASP、LAMMPS 或训练日志中的临时结果的原因:看完即走,不需要管理一堆下载文件。
Kitty 官方文档把图形协议定义为类似下面的控制序列:
ESC _ G <控制数据> ; <base64 图像数据> ESC \\
远端客户端不能假设和终端共享文件系统,因此协议支持把数据切成多个块直接传输。我们的实现使用 PNG、base64 和 4096 字节分块,并根据终端单元大小选择显示尺寸。协议的完整定义见 Kitty graphics protocol。
2. 我们的 icat 和 Kitty 官方 icat 有什么不同?
Kitty 官方的 kitten icat 更成熟,支持 ImageMagick 能识别的多种格式、目录浏览、放置位置、tmux passthrough 等功能。只使用 Kitty 的用户优先使用官方版本。
我们的 icat 是一个很小、容易拷贝到集群上的 Python 脚本,重点放在自己的科研工作流:
- 只依赖 Python 和 Pillow,不要求安装 Kitty 本体;
- 可以在支持 Kitty graphics protocol 的终端或远端前端中输出图像;
- 支持
--no-hold、--clear、多图片和终端单元尺寸探测; - 发现输入是
POSCAR、CONTCAR、XDATCAR、CIF、XYZ、LAMMPS trajectory 等结构文件时,自动转交ovicat; - 在无法查询终端像素尺寸时,使用保守的字符宽高比作为 fallback。
这不是对 Kitty 官方工具的全面替代,而是一个“能和我们的结构预览串起来、能放在没有 Kitty 安装权限的计算节点上”的薄封装。
如果你的终端不支持 Kitty graphics protocol,可以使用 Chafa 或 viu 这类工具。它们会根据终端能力输出 ANSI/Unicode 字符画、Sixel 或其他图形协议,兼容性更广,但输出不一定是 Kitty 协议的原始像素图。WezTerm 也提供自己的 wezterm imgcat,它主要面向 iTerm2 inline images protocol;选哪个取决于你的终端环境。
3. ovicat:为什么还需要一个包装器?
OVITO 本身已经有 Python API 和命令行解释器。ovicat 不重新实现结构可视化,它只是把这几个步骤固定下来:
POSCAR / CONTCAR / dump / CIF
↓
OVITO Python pipeline
↓
PNG(默认等轴视图、Tachyon、1200×900)
↓
icat 在终端内显示
这样做的好处是,用户不必先打开 OVITO GUI、手动导出 PNG、再切回 SSH 终端。常用命令就是:
ovicat CONTCAR
ovicat model.cif --view top
ovicat md.dump --frame 50 --view side --save frame50.png
ovicat start.lmp --view side --ball-stick
渲染器还支持 --color-by、--color-range、--label-elems 和 --paired-plain,适合快速检查结构、原子属性和轨迹帧。它仍然受 OVITO 支持的文件格式和渲染器限制,不会替代正式论文出图流程。
4. 新用户的前置条件和安装
icat/ovicat 的源码在 tools/icat/ 中:
git clone https://github.com/hn-yu/hn-yu.github.io.git
cd hn-yu.github.io
# icat 的 Pillow 依赖
python3 -m venv .venv-icat
source .venv-icat/bin/activate
python -m pip install Pillow
# 使用仓库中的命令
export PATH="$PWD/tools/icat:$PATH"
icat figure.png
ovicat CONTCAR
必须满足的条件是:
- 终端支持 Kitty graphics protocol;Kitty 是最直接的选择,其他终端或终端复用器需要实际确认其 passthrough 支持。
icat所用 Python 环境安装了 Pillow。ovicat需要uv。脚本使用 PEP 723 元数据,第一次运行时由uv创建隔离环境并安装 OVITO;也可以按 OVITO 官方安装文档 自己安装 Python 模块。- 如果只是查看 PNG/JPG,不需要 OVITO;只有使用结构和轨迹自动渲染时才需要
ovicat的 OVITO 依赖。
二、turm:把 Slurm 作业管理变成一个轻量 TUI
1. 上游 turm 做什么?
上游 karimknaebel/turm 是一个 Slurm TUI。它调用熟悉的 squeue 获取作业列表,配合 scancel、scontrol 和日志文件监控,在终端中查看作业状态、输出、错误和时间限制。它的设计取向很清楚:不依赖 Slurm C API,而是使用集群普遍都有的 CLI。
这和直接运行 squeue 的区别,不是“显示更多字段”这么简单,而是把作业列表、日志尾部、取消作业和筛选操作放进同一个可交互界面。squeue 的筛选参数仍然可以通过 turm 传入;例如:
turm --me --sort=-id --states=ALL
2. 我们的 fork 增加了什么?
我们的 hn-yu/turm README 已明确标注这是个人 fork,不是准备合并回上游的兼容发行版。它保留上游的 Slurm TUI 核心,但把我自己每天重复做的动作绑定到了按键上:
Enter:退出 TUI,并在选中作业的工作目录中打开 shell;n:SSH 到作业的第一个节点,GPU 作业启动nvitop,CPU 作业启动htop,再 fallback 到top;y:使用 OSC 52 把 Job ID 复制到本地终端剪贴板,SSH 场景也能用;- 鼠标拖动:调整 Jobs/Details 两个面板的宽度;
- 保留 stdout/stderr 切换、日志换行、取消信号和时间限制编辑等功能。
因此它不是一个新 Slurm 调度器,也不是 nvitop 的替代品。它更像是:
turm ≈ watch -n2 squeue + tail -f slurm-log.out + 几个集群工作流快捷键
3. turm 的前置条件
turm 目前没有发布到 PyPI、crates.io 或 conda-forge,也不能直接用 uv tool install turm 得到这个 fork。需要 Rust 工具链:
rustc --version
cargo --version
cargo install --git https://github.com/hn-yu/turm
实际运行还需要:
- 集群上的 Slurm CLI,至少能找到
squeue;取消和修改作业需要scancel/scontrol; n进入节点监控时需要 SSH 能连到作业节点;某些集群只允许连接到自己有活动作业的节点;- GPU 节点需要在远端节点的 PATH 中有
nvitop,例如pip install --user nvitop或使用uvx nvitop; - CPU 节点需要
htop,没有时 fork 会尝试top; y的 OSC 52 复制需要本地终端支持 OSC 52。它不依赖把剪贴板内容写进集群文件。
注意:nvitop 是 GPU 进程监视器,不会因为安装了 turm 就自动出现在所有计算节点上。若集群使用共享 home,通常只需安装一次;否则要按照集群环境分别安装。
三、vasp-lsp:让 INCAR 在保存提交前就能说话
1. 上游能力
上游 newtontech/VASP-LSP 是 VASP 输入/输出文件的 Language Server,目标是把 INCAR、POSCAR、KPOINTS 放进编辑器之后,提供补全、hover、格式化、定义跳转、引用查找和实时诊断。它使用标准 LSP,所以编辑器端负责 UI,VASP-LSP 负责语法、schema 和语义检查。
这和一个只做语法高亮的 Vim/Neovim filetype 插件不同:LSP 可以在你输入时告诉你“标签不存在”“值类型错误”“两个参数冲突”,并给出补全或 quick fix。它也不运行 VASP 本身,不会替你决定 ENCUT、MAGMOM 或收敛标准是否适合科研问题。
2. 我们的 fork 修了什么?
现在使用的是 hn-yu/VASP-LSP 的 main。相对于当时使用的上游 v0.4.5,我们做了几类面向真实 INCAR 的修复:
- 从 VASP 官方 Wiki 整理并载入一批常用 INCAR keyword,避免
ISYM、LASPH、LREAL、NUPDOWN等合法标签被误报为 Unknown INCAR tag; - 修复通用布尔值的语义校验,使
.FALSE.解析成 PythonFalse后仍能和 schema 中的.FALSE.正确匹配,同时保留Auto、On等混合枚举值; - 修复 Neovim 输入一段内容后 diagnostics 消失的问题:服务端正确处理 Full/Incremental
textDocument/didChange,并在配置中保留旧版本 pygls 的 Full-sync 兼容处理; - 增加 Fe2 INCAR fixture、schema regression tests、增量同步测试和 headless Neovim 集成测试;
- 重新整理 Neovim 0.11+ native LSP 配置,让新人不必拼出一长串隐含设置。
这个 fork 仍然保留上游的包版本号 0.4.5;它是 GitHub 上的个人维护版本,不应和 PyPI 上同名的发行版混为一谈。需要使用这些修复时,应明确从 fork 安装。
3. Neovim/LazyVim 的最短路径
服务端先安装到 PATH:
uv tool install --force git+https://github.com/hn-yu/VASP-LSP.git
which vasp-lsp
vasp-lsp --version
Neovim 推荐 0.11 或更新版本。Neovim 原生 LSP 文档现在使用 vim.lsp.config() / vim.lsp.enable();我们已经把可以直接复制的配置放在 fork 的 editors/neovim/lsp/vasp_lsp.lua:
mkdir -p ~/.config/nvim/lsp
curl -fsSL \
https://raw.githubusercontent.com/hn-yu/VASP-LSP/main/editors/neovim/lsp/vasp_lsp.lua \
-o ~/.config/nvim/lsp/vasp_lsp.lua
在 init.lua 中启用:
vim.cmd("filetype plugin indent on")
vim.lsp.enable("vasp_lsp")
如果使用 LazyVim,常见配置路径会使用 neovim/nvim-lspconfig。LazyVim 通常已经带着它;如果你裁剪过默认插件,需要先把它加入插件列表,再让 vim.lsp.enable("vasp_lsp") 在插件初始化后执行:
return {
{
"neovim/nvim-lspconfig",
opts = function()
vim.lsp.enable("vasp_lsp")
end,
},
}
这里要区分两件事:nvim-lspconfig 是 LazyVim 常见的配置/服务器集合插件,不是 VASP-LSP Python 服务端本身的依赖;Neovim 0.11 的 native config 可以不单独依赖它。无论哪种路径,vasp-lsp 可执行文件必须在启动 Neovim 的 PATH 中。
打开 INCAR 后,可以用下面的命令检查是否真正连上:
:checkhealth vim.lsp
:LspInfo
OUTCAR、OSZICAR 和 Slurm 输出文件的 filetype 可能需要额外映射。完整说明见 fork 的 Neovim setup guide。
三个工具放在一起时的工作流
这三个工具并不是一个统一平台,却可以自然地串起来:
提交作业
↓
turm --me 查看队列、进入工作目录、监控节点
↓
编辑 INCAR
↓
vasp-lsp 在 Neovim 中补全和诊断
↓
运行 VASP / LAMMPS
↓
ovicat CONTCAR 在 SSH 终端里检查结构
icat figure.png 直接查看结果图
它们分别站在三个边界上:
icat/ovicat不负责传文件,也不负责科学解释,只负责把结果更快地呈现出来;turm不负责调度策略,只负责把 Slurm 已经提供的信息和常用操作放到一个 TUI;vasp-lsp不负责运行 VASP,也不替代研究者判断参数,只负责在输入文件进入队列前发现明显问题。
如果只想找成熟的通用工具,优先使用它们的上游项目:Kitty 的 icat、上游 turm、上游 VASP-LSP 和 OVITO 官方 Python API。我们的 fork 和包装器的价值不在于声称“重新发明了这些工具”,而在于把个人集群和 VASP 工作流中反复遇到的最后一公里补上。