本文由 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. icatscprsync 有什么不同?

先把一个容易混淆的概念分开:

  • scprsync 的任务是传输或同步文件。它们把字节放到另一台机器的文件系统里。
  • 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、多图片和终端单元尺寸探测;
  • 发现输入是 POSCARCONTCARXDATCAR、CIF、XYZ、LAMMPS trajectory 等结构文件时,自动转交 ovicat
  • 在无法查询终端像素尺寸时,使用保守的字符宽高比作为 fallback。

这不是对 Kitty 官方工具的全面替代,而是一个“能和我们的结构预览串起来、能放在没有 Kitty 安装权限的计算节点上”的薄封装。

如果你的终端不支持 Kitty graphics protocol,可以使用 Chafaviu 这类工具。它们会根据终端能力输出 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

必须满足的条件是:

  1. 终端支持 Kitty graphics protocol;Kitty 是最直接的选择,其他终端或终端复用器需要实际确认其 passthrough 支持。
  2. icat 所用 Python 环境安装了 Pillow。
  3. ovicat 需要 uv。脚本使用 PEP 723 元数据,第一次运行时由 uv 创建隔离环境并安装 OVITO;也可以按 OVITO 官方安装文档 自己安装 Python 模块。
  4. 如果只是查看 PNG/JPG,不需要 OVITO;只有使用结构和轨迹自动渲染时才需要 ovicat 的 OVITO 依赖。

二、turm:把 Slurm 作业管理变成一个轻量 TUI

1. 上游 turm 做什么?

上游 karimknaebel/turm 是一个 Slurm TUI。它调用熟悉的 squeue 获取作业列表,配合 scancelscontrol 和日志文件监控,在终端中查看作业状态、输出、错误和时间限制。它的设计取向很清楚:不依赖 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-LSPmain。相对于当时使用的上游 v0.4.5,我们做了几类面向真实 INCAR 的修复:

  • 从 VASP 官方 Wiki 整理并载入一批常用 INCAR keyword,避免 ISYMLASPHLREALNUPDOWN 等合法标签被误报为 Unknown INCAR tag;
  • 修复通用布尔值的语义校验,使 .FALSE. 解析成 Python False 后仍能和 schema 中的 .FALSE. 正确匹配,同时保留 AutoOn 等混合枚举值;
  • 修复 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

OUTCAROSZICAR 和 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 工作流中反复遇到的最后一公里补上。

相关链接