Fortran的Namelist

3 minute read

Published:

基于 Namelist 的 Fortran 配置读写

Namelist 是 Fortran 标准提供的免格式(format-free)键值输入输出机制:把一组变量绑定到一个组名,再用 READ/WRITE 的 NML= 说明符整组读写。该能力最早由部分 FORTRAN 77 编译器扩展引入,自 Fortran 90 起进入标准,并沿用至 Fortran 2018。科学计算程序(数值天气预报、气候模式、轨道与 GNSS 处理等)普遍用 .nml 文件作为运行时配置,从而避免为每个参数手写格式串。

依据 ISO/IEC Fortran 2018 第 8.9 节与第 13.11 节,以及 Intel Fortran NAMELIST 说明、Programming in Modern Fortran — Namelists,下文给出可直接编译运行的写法与常见陷阱。

1. 为何使用 Namelist

与固定格式 READ(10,'(2I5)') 或手写解析器相比,Namelist 的工程价值体现在三点。

第一,按名赋值:输入记录不必覆盖组内全部变量,未出现的变量保持原值;赋值顺序与 NAMELIST 声明顺序无关。第二,人机可读:配置文件是纯文本,可用编辑器直接改参数后重跑,无需重编译。第三,类型覆盖面广:标量、数组、字符、逻辑、复型以及派生类型分量均可进入同一组。

适用边界同样明确:Namelist 面向结构化参数表,不适合替代二进制大规模场数据,也不适合需要严格模式校验的复杂 schema(此类场景可另用 JSON/YAML 外挂解析,或与 Namelist 分层配合)。

2. 声明语法

标准形式为:

NAMELIST /namelist-group-name/ variable-name-list

同一作用域内,同一组名可多次出现,后继列表视为对该组的续写;同一变量也可出现在多个组中。约束包括:组内对象不得为假定大小数组(assumed-size);组名不得经 USE 关联引入;对象须在本作用域显式/隐式定型,或经 host/USE 关联可见。

integer :: a, b
real    :: x
namelist /ab/ a, b
namelist /ab/ x          ! 等价于向组 ab 追加 x

亦可在一条语句中定义多组:

namelist /input/ name, grade, date /output/ total, name

3. 外部文件格式

每个 Namelist 记录以 &组名 起始,以单独的 / 结束;中间为 名字=值 序列。名字大小写不敏感(FOO 与 foo 指向同一对象)。同一文件可存放多组;多数编译器允许在记录中使用 Fortran 注释风格内容,但可移植写法宜保持记录体干净。

&ab
a = 1,
b = 2
/

数组与派生类型用下标或 % 分量名指定:

&EXAMPLE
X = 100
Y = 500
R(1) = 10.5
R(2) = 20.25
PERSON%ID = 1
PERSON%NAME = 'Alice'
PERSON%AGE = 27
/

逻辑值写作 .TRUE. / .FALSE.(或编译器接受的缩写形式,如 T/F,以所用编译器文档为准)。字符串用单引号或双引号包围。

4. 最小完整示例

原稿中 /namelist/ab/a,b/ 与输入文件首行 /& 均不符合标准。标准声明为 namelist /ab/ a, b;输入必须以 &ab 开头。下面给出与原稿意图一致、按 Fortran 2018 风格改写的可运行版本。

程序 ab_demo.f90:

program ab_demo
  use, intrinsic :: iso_fortran_env, only: output_unit, error_unit
  implicit none

  integer :: a, b
  integer :: fu, ios
  namelist /ab/ a, b

  a = 0
  b = 0

  open (newunit=fu, file='ab.namelist', status='old', action='read', iostat=ios)
  if (ios /= 0) then
    write (error_unit, '(a)') '无法打开 ab.namelist'
    stop 1
  end if

  read (fu, nml=ab, iostat=ios)
  if (ios /= 0) then
    write (error_unit, '(a)') 'Namelist 读取失败:请检查组名与赋值语法'
    close (fu)
    stop 2
  end if
  close (fu)

  write (output_unit, fmt='(2(1x,i0))') a, b
  ! 也可回写标准输出核对:
  ! write (output_unit, nml=ab)
end program ab_demo

配置文件 ab.namelist:

&ab
a = 1,
b = 2
/

编译与运行:

gfortran -std=f2018 -o ab_demo ab_demo.f90
./ab_demo
# 输出: 1 2

读写控制流可概括为:

flowchart LR
  A[声明变量] --> B["namelist /组名/ 变量列表"]
  B --> C[open 配置文件]
  C --> D["read/write(..., nml=组名)"]
  D --> E[按 iostat 判定成败]
  E --> F[close]

5. 读写 API 要点

READ / WRITE 使用 NML= 指定组名;可与 UNIT=、IOSTAT=、IOMSG=、ERR=、END= 等说明符组合。当省略 NML= 关键字时,单元号须为第一说明符、组名须为第二说明符,且不得再写格式说明符。

read (unit=fu, nml=ab, iostat=ios, iomsg=msg)
write (unit=fu, nml=ab)

输出顺序与 NAMELIST 中对象声明顺序一致;输入顺序任意。组内变量若未在输入中出现,则保持调用前的值——因此应在 READ 之前赋予合理缺省值,作为“可选配置项”的基线。

DELIM= 说明符可控制字符输出的引号风格(如 DELIM='QUOTE'),便于生成可再被 Namelist 读回的文本。

6. 数组、派生类型与多组文件

6.1 数组

可对整段赋值,也可对单个元素或片段赋值。Intel 文档示例中,连续元素可写为:

&example
iarray(8) = 41, 42, 43
/

表示自下标 8 起依次填入后续常量。

6.2 派生类型

将类型变量本身列入 Namelist 后,输入侧用 变量%分量 寻址。推荐在 READ 前用构造函数设定缺省,读取失败时仍可回退到缺省人:

type :: person_type
  integer :: id
  character(len=32) :: name
  integer :: age
end type

type(person_type) :: person
integer :: x, y
real :: r(2)
namelist /EXAMPLE/ x, y, r, person

person = person_type(-1, 'Jane Doe', 0)
x = 0; y = 0
r = [0.0, 0.0]

6.3 单文件多组

同一文件可串联多段 &组名 ... /。程序按组名分别 READ:先读物理参数组,再读数值积分组,互不干扰。组名在输入记录中必须与源码中的 Namelist 组名一致。

7. 稳健封装模式

生产代码宜把打开、读取、关闭与错误信息集中到子程序,并在失败时保留缺省值:

subroutine read_ab_namelist(path, a, b, ok)
  character(len=*), intent(in)  :: path
  integer,          intent(inout) :: a, b
  logical,          intent(out) :: ok
  integer :: fu, ios
  namelist /ab/ a, b

  ok = .false.
  open (newunit=fu, file=path, status='old', action='read', iostat=ios)
  if (ios /= 0) return
  read (fu, nml=ab, iostat=ios)
  close (fu)
  ok = (ios == 0)
end subroutine read_ab_namelist

跨语言维护配置时,可用 Python 侧的 f90nml 解析与改写 .nml,再交 Fortran 运行时读取,避免两边各维护一份手工格式约定。

8. 与其它配置方式的对照

方式优点局限
Namelist标准内建、按名可选赋值、科学计算生态成熟schema 弱;扩展语法因编译器而异
格式化 READ紧凑、确定性高改字段需同步改格式与文档
命令行参数适合少量开关长参数表难维护
外挂 JSON/YAML工具链丰富、校验容易需额外库或自写解析

气候与地球系统模式(如 CESM 及相关分量)广泛依赖 Namelist 作为组件级控制面:改物理开关、时间步、输出频率往往只需编辑 .nml,不必触碰源码。

9. 常见错误与修正

原稿示例与实践中的高频问题可对照下表排查。

现象原因修正
编译失败:/namelist/.../非标准声明写法使用 namelist /组名/ 变量列表
运行时读失败文件以 /& 或缺少组名开头使用 &组名 起始、/ 结束
部分变量仍为 0输入未给出该名,且读前未设缺省读前赋缺省;或补全输入项
字符截断或错位长度声明不足 / 引号不配对加长 character(len=...);检查引号
假定大小数组入组标准禁止改为显式形状或可分配数组后再入组
回退读失败对 list-directed/namelist 记录做 BACKSPACE标准禁止对该类记录回退

另需注意:多数环境中 Namelist 输入名大小写不敏感,但字符值本身区分大小写;逻辑与复型字面量须符合 Fortran 词法,勿混用其它语言的 true/false。

10. 小结

Namelist 把“变量集合”提升为一等配置对象:源码侧用 NAMELIST 声明组,文件侧用 &组名 ... / 提供键值,I/O 侧用 NML= 一次完成读写。正确写法是标准声明加标准记录头,并在读取前设置缺省、用 IOSTAT 判定失败。掌握数组下标、派生类型 % 分量与多组文件后,即可覆盖数值程序中绝大多数运行时参数管理需求。

参考文献

  1. ISO/IEC JTC1/SC22/WG5. Fortran 2018 Final Draft International Standard(N2155),§8.9 NAMELIST statement,§13.11 Namelist formatting. https://wg5-fortran.org/N2151-N2200/ISO-IECJTC1-SC22-WG5_N2155_Fortran_2018_Final_Draft_International_Standard.pdf
  2. Intel Corporation. NAMELIST — Intel® Fortran Compiler Developer Guide and Reference. https://www.intel.com/content/www/us/en/docs/fortran-compiler/developer-guide-reference/2025-1/namelist.html
  3. Programming in Modern Fortran. Namelists. https://cyber.dabamos.de/programming/modernfortran/namelists.html
  4. Oracle. NAMELIST I/O (FORTRAN 77 Language Reference). https://docs.oracle.com/cd/E19957-01/805-4939/6j4m0vnc6/index.html
  5. Ward, M. f90nml — Python namelist parser for Fortran. https://github.com/marshallward/f90nml