pSolve enter 指令使用说明

· 预计阅读 10 分钟

enter 是 pSolve 的底层启动程序。它接收用户缩写和运行参数,检查工作文件,设置运行状态,再把控制权交给交互程序 OPTIN 或批处理程序 BATCH。从这个意义上说,enter 主要承担启动和调度接口的作用。VLBI 数据读取、理论时延计算、参数估计和批处理控制文件的解析,由后续程序完成。我在巴黎天文台学习 Calc/Solve 软件的使用时,导师直接使用 enter 来进行全局解算。而现在,日常使用通常从 psolve 启动。理解 enter,主要是为了弄清 psolve 如何传递参数,以及启动失败、恢复运行和工作目录问题应当如何排查。

1. 命令格式与位置参数

enter 对应的源代码文件为 psolve/progs/solve/enter/enter.f。enter 没有常见的 --help、--silent 等选项解析器。它读取前七个位置参数。用参数名称表示,其调用格式为:

enter initials control_file post_flag run_mode test_flag processor_index processor_count

其中 post_flag 等名称是本文为便于解释使用的标签,并非可以直接输入的选项名。

位置 参数 含义 常用值 源码行为
1 initials 用户缩写 如 NL 转为大写,存入两个字符的 LETT,再由 LETOK 检查
2 control_file 控制文件或交互标记 0 或控制文件路径 空参数或通常的 0 表示交互模式;其余非空值触发批处理模式
3 post_flag 通用停用检查开关及历史 proceed 字段 0 首字符不是 0 时执行 POST_CHECK;批处理时还转交给 BATCH
4 run_mode 内部前后台及恢复策略 f、b、a、n 设置 PRE_IP(2) 的第 6、7、8 位
5 test_flag 测试版本替换开关 0,开发调试用 -1 前两个字符为 -1 时启用测试版本选择
6 processor_index 任务分片编号 1 原始参数字符串传入 BATCH;本层数值检查存在问题
7 processor_count 任务分片总数 1 检查范围为 1–128;省略时第 6、7 项都重置为 1

这些参数是位置相关的。如果需要设置后面的参数,就要保留前面的占位值。普通运行中,第 3、5 项通常取 0,第 6、7 项取 1;第 4 项则根据运行和恢复需求选择。

例如,交互模式的调用为:

enter NL 0 0 f 0 1 1

使用批处理控制文件、从头开始解算的调用为:

enter NL solution.cnt 0 n 0 1 1

不带参数调用 enter 时,在通过前面的运行停用检查后,会进入交互流程并询问用户缩写。在这一输入提示处输入 :: 可以退出,源码返回状态为 1。

第 4 个参数run_mode用于控制两件事:一是程序遇到错误时,等待用户处理还是直接退出;二是批处理任务中断后,再次启动时如何处理上次的运行进度。它可以取 f、b、a 或 n,不区分大小写,具体含义见下表。

run_mode 遇到错误时 再次启动批处理任务时
f 等待用户处理 如果可以恢复,询问用户是否继续上次的运行
b 退出程序 如果可以恢复,仍会询问用户
a 退出程序 如果可以恢复,自动继续上次的运行
n 退出程序 不恢复上次的进度,从头开始

2. enter 的运行逻辑

enter 的启动过程如下图所示(根据ChatGPT的输出生成),可分为:

flowchart TD
    A(["启动 enter"]) --> B["读取前七个参数<br/>用户缩写转为大写"]

    B --> C{"第 3 项首字符为 0?"}
    C -- 否 --> D["调用 POST_CHECK"]
    D --> E{"存在通用停用标记?"}
    E -- 是 --> X(["退出程序"])
    E -- 否 --> F
    C -- 是 --> F["根据第 2 项确定运行模式"]

    F --> G{"批处理模式?"}
    G -- 是 --> H{"存在批处理专用停用标记?"}
    H -- 是 --> X
    H -- 否 --> I
    G -- 否 --> I["保存终端状态<br/>建立通信管道"]

    I --> J{"交互模式?"}
    J -- 是 --> K["启动 curses 界面<br/>设置初始信号处理"]
    J -- 否 --> L["设置初始信号处理"]
    K --> M
    L --> M{"交互模式且未提供用户缩写?"}

    M -- 是 --> N["询问用户缩写"]
    N --> O{"输入为 ::?"}
    O -- 是 --> X
    O -- 否 --> P
    M -- 否 --> P["调用 LETOK 检查用户缩写"]

    P --> Q{"用户缩写有效?"}
    Q -- 否 --> R{"交互模式?"}
    R -- 是 --> S["清空第 1 项,重新询问"]
    S --> N
    R -- 否 --> X

    Q -- 是 --> T["交互模式关闭 curses<br/>调用 SETUP_PRELUDE"]
    T --> U["设置前后台与恢复位<br/>保存 termxx<br/>重新注册信号处理"]
    U --> V["设置测试版本<br/>处理并检查分片参数"]
    V --> W{"参数检查通过?"}
    W -- 否 --> X
    W -- 是 --> AA["查找并打开 CONFxx<br/>读取创建日期"]

    AA --> AB{"存在且打开、读取成功?"}
    AB -- 否 --> X
    AB -- 是 --> AC{"创建日期满足兼容要求?"}
    AC -- 否 --> X
    AC -- 是 --> AD["从 GLBFxx 恢复 spool 设置<br/>记录启动命令"]

    AD --> AE["检查运行占用标记"]
    AE --> AF{"允许继续运行?"}
    AF -- 否 --> X
    AF -- 是 --> AG["建立运行占用标记 LOCKxx"]
    AG --> AH["调用 CHKSC<br/>检查十种主要工作文件"]
    AH --> AI{"所需工作文件均存在?"}
    AI -- 否 --> X
    AI -- 是 --> AJ["读取并写回 GLBFxx<br/>批处理时设置 KUSER_PART 为假"]

    AJ --> AK{"批处理模式?"}
    AK -- 否 --> AL(["以 PASS 方式调度 OPTIN"])
    AK -- 是 --> AM["构造 NEWSTR 启动字符串<br/>包含 xxx.cnt 文件名"]
    AM --> AN["USE_BUFFER 写入<br/>128 字节通信缓冲区"]
    AN --> AO(["以 PASS 方式调度 BATCH"])
  • 读取命令行参数。 读取前七个参数,并将用户缩写转为大写,例如将 nl 转为 NL。
  • 检查是否允许启动。 根据第 3 个参数,决定是否检查通用停用文件。如果需要检查且该文件存在,程序就会退出。随后根据第 2 个参数确定采用交互模式还是批处理模式;批处理模式还要检查专用的停用文件。
  • 准备终端界面和程序间通信。 保存当前终端的设置,并建立供后续程序传递信息的通信管道。如果采用交互模式,再启动终端菜单界面。这里必须先建立管道,再启动界面。
  • 检查用户缩写。 调用 LETOK,检查用户缩写是否允许使用。如果没有通过检查,交互模式会让用户重新输入,批处理模式则直接退出。
  • 准备运行环境。 完成内部初始化,根据参数确定遇到错误时如何处理,以及是否恢复上次的运行。同时,将终端设置保存到 termxx 文件,设置收到中断等系统信号时的处理方式,并处理测试版本选项、任务分片编号和总数。
  • 检查工作文件的配置信息。 读取 CONFxx,确认文件存在且能够读取,再检查其中记录的创建日期,判断这组工作文件是否符合当前版本的要求。这里读取的是工作文件的配置信息,不是用户提供的批处理控制文件 xxx.cnt。
  • 读取输出设置并记录启动命令。 从 GLBFxx 中读取此前保存的 spool 输出设置,并将本次启动命令的部分内容保存到该文件中。
  • 检查这组工作文件是否已被占用。 检查是否已有任务使用当前工作目录中的同一用户缩写。如果已有任务占用,程序就会退出;否则,建立本次运行的运行占用标记 LOCKxx,供其他任务检查。同一工作目录下,不应同时运行使用同一用户缩写的两个 pSolve 任务。
  • 检查所需的工作文件是否齐全。 调用 CHKSC,检查十种主要工作文件是否存在;缺少所需文件时退出。批处理模式还会先关闭用户自定义偏导数标记,后续再根据控制文件中的设置决定是否启用。
  • 启动后续程序。 交互模式进入 OPTIN,由用户通过菜单继续操作;批处理模式则将控制文件名等启动信息传给 BATCH,由它继续读取控制文件并执行批处理任务。

CHKSC 只检查文件是否存在,并不逐一打开文件验证可读写性,也不检查文件大小或二进制内容。这里涉及的十种工作文件前缀为:

PARF  CORF  NRMF  PLTF  RESF  OBSF  COMM  NAMF  GLBF  SARF

每个前缀后面都附加用户缩写。例如,NL 对应 PARFNL、CORFNL 等文件。

3. 分片参数的作用与实现问题

第6个参数processor_index和第7个输入参数processor_count分别表示任务分片编号与总数。这两个参数参与批处理中的数据(session)分配:BATCH 根据它们判断当前任务应当处理哪些观测数据,并跳过其余数据。 具体划分发生在 psolve/progs/solve/batch/prces.f 中,核心代码是:

IM = MOD( IND_SES, NUM_PROC )
IF ( IM == 0 ) IM = NUM_PROC

IF ( IM .EQ. IND_PROC ) THEN
     FL_SKIP = .FALSE.
   ELSE
     FL_SKIP = .TRUE.
END IF

这里,IND_SES 是当前数据序号,IND_PROC 和 NUM_PROC 分别是任务编号和任务总数。

例如,将 12 个数据分给 4 个任务时,分配结果为:

任务编号 任务总数 负责的 session 序号
1 4 1、5、9
2 4 2、6、10
3 4 3、7、11
4 4 4、8、12

因此,这是一种按数据序号循环分配任务的机制。不过,它们不是 OpenMP 线程数开关;enter 不会因为把最后一个数字改成 4,就自动启动四个独立任务。多个任务仍需要由外部脚本或作业调度系统组织运行。当前 psolve 启动脚本的常规分支将最后两个参数固定为 1 1,因此通过这些分支启动时,所有 session 都由同一个任务处理。这说明常规调用没有启用分片功能。这种分片机制可能缩短全局解算的总运行时间,但实际加速效果仍需测试,仅凭enter.f还无法推断。

需要指出,enter.f 中存在一处明确的参数检查问题:

CALL CHIN ( ARG_STR(7), M_PROC_I4 )
...
CALL CHIN ( ARG_STR(7), I_PROC_I4 )

第二次调用按上下文应当读取第 6 项,但实际仍读取第 7 项。结果是 enter 没有按设计验证第 6 项,而且后面的 I_PROC_I4 > M_PROC_I4 检查失去了预期作用。不过,构造发送给 BATCH 的启动字符串时,代码仍然使用原始的 ARG_STR(6) 和 ARG_STR(7)。因此,这只是参数校验错误,不影响解算。