如何处理 Fortran 名单中的可选组
How to handle an optional group in a Fortran Namelist
我正在使用最初用 Fortran 77 编写的代码,该代码使用名称列表(在编写时由编译器扩展支持 - 此功能仅在 Fortran 90 中成为标准)来读取输入文件。名单输入文件在(多个)纯文本 header 和页脚(参见 example.nml
)之间有一组名单变量。仅当先前读取的变量满足某些条件时,才会读取某些名称列表变量组。
当依次读取文件中的所有名单组时,使用 gfortran、ifort 和 nagfor 编译的可执行文件的行为都相同,并给出预期的输出。但是,当要跳过输入文件中的给定名称列表组时(可选读取),gfortran 和 ifort 可执行文件会根据需要处理此问题,而使用 nagfor 编译的可执行文件会引发运行时错误:
Runtime Error: reader.f90, line 27: Expected NAMELIST group /GRP3/ but found /GRP2/
Program terminated by I/O error on unit 15 (File="example.nml",Formatted,Sequential)
作为重现问题的最小工作示例,请考虑下面给出的名单文件 example.nml
和 driver 程序 reader.f90
,其中 NUM2
来自名单组 [=只有当 NUM1
from namelist group GRP1
等于 1
:
时才应读取 19=]
example.nml:
this is a header
&GRP1 NUM1=1 /
&GRP2 NUM2=2 /
&GRP3 NUM3=3 /
this is a footer
reader.f90:
program reader
implicit none
character(len=40) :: hdr, ftr
integer :: num1, num2, num3, icode
! namelist definition
namelist/grp1/num1
namelist/grp2/num2
namelist/grp3/num3
! open input file
open(unit=15, file='example.nml', form='formatted', status='old', iostat=icode)
! read input data from namelists
read(15, '(a)') hdr
print *, hdr
read(15, grp1)
print *, num1
if (num1 == 1) then
read(15, grp2)
print *, num2
end if
read(15,grp3)
print *, num3
read(15, '(a)') ftr
print *, ftr
! close input file
close(unit=15)
end program reader
当 NUM1=1
:
时,所有可执行文件都会给出预期的输出
this is a header
1
2
3
this is a footer
然而,当例如NUM1=0
,用 gfortran 和 ifort 编译的可执行文件给出了所需的输出:
this is a header
0
3
this is a footer
当使用 nagfor(以严格符合标准而著称)编译的可执行文件时,读取 header 和第一个名单组:
this is a header
0
但随后因前面提到的运行时错误而终止。
如错误消息所示,example.nml
是按顺序访问的,如果是这种情况,/GRP2/ 是下一个要读取的记录,而不是程序逻辑要求的 /GRP3/,所以错误消息对我来说很有意义。
所以我的问题是:
- 所显示的行为是否可以归因于 nagfor 而不是 gfortran 和 ifort 强制执行的标准(非)一致性?
- 如果是这样,这是否意味着使用 gfortran 和 ifort 观察到的 non-sequential 读数是由于这些编译器(而不是 nagfor)支持的扩展?可以使用编译器标志将其 on/off 转换吗?
- 我能想到的最简单的解决方法(对现有大型程序的最小更改)是在
else
分支中为 if
语句添加一个虚拟 read(15,*)
reader.f90
。这似乎适用于所有提到的编译器。这会使代码符合标准(Fortran 90 或更高版本)吗?
这些是用于编译可执行文件的编译器版本和选项:
- GNU Fortran (Ubuntu 9.1.0-2ubuntu2~18.04) 9.1.0:
gfortran -Wall -Wextra -fcheck=all -g -Og -fbacktrace reader.f90
- 英特尔(R) Visual Fortran,版本 16.0 内部版本 20160415:
ifort -Od -debug:all -check:all -traceback reader.f90
- NAG Fortran Compiler Release 6.1(Tozai) Build 6116:
nagfor -O0 -g -C reader.f90
当请求对外部文件进行名单格式化时,名单记录从文件当前位置的记录开始。
名单输入记录的结构由语言规范明确定义(例如,参见 Fortran 2018 13.11.3.1)。特别是,这不允许不匹配的名单组名称。 nagfor 对此的抱怨是合理的。
几个编译器似乎确实会继续跳过记录,直到在记录中识别出名单组,但我不知道可用于控制该行为的编译器标志。从历史上看,情况通常是使用不同的文件指定多个名单。
来到你的"simple workaround":唉,这在一般情况下是不够的。名单输入可能会消耗外部文件的多个记录。 read(15,*)
只会将文件位置向前移动一条记录。您将要前进到名单的终止记录之后。
当您知道名单只是那条记录时,解决方法就很好了。
@francescalus' 并对该答案发表评论,清楚地解释了我问题的前两部分,同时指出了第三部分的缺陷。希望它对其他偶然发现遗留代码类似问题的人有用,这是我最终实施的解决方法:
本质上,解决方案是确保文件记录标记始终定位正确在尝试任何名单组读取之前。这种定位是在一个子程序中完成的,该子程序倒带输入文件,读取记录直到找到具有匹配组名的记录(如果没有找到,可以引发 error/warning),然后倒带并将文件记录标记重新定位到准备好阅读名单。
subroutine position_at_nml_group(iunit, nml_group, status)
integer, intent(in) :: iunit
character(len=*), intent(in) :: nml_group
integer, intent(out) :: status
character(len=40) :: file_str
character(len=:), allocatable :: test_str
integer :: i, n
! rewind file
rewind(iunit)
! define test string, i.e. namelist group we're looking for
test_str = '&' // trim(adjustl(nml_group))
! search for the record containing the namelist group we're looking for
n = 0
do
read(iunit, '(a)', iostat=status) file_str
if (status /= 0) then
exit ! e.g. end of file
else
if (index(adjustl(file_str), test_str) == 1) then
! backspace(iunit) ?
exit ! i.e. found record we're looking for
end if
end if
n = n + 1 ! increment record counter
end do
! can possibly replace this section with "backspace(iunit)" after a
! successful string compare, but not sure that's legal for namelist records
! thus, the following:
if (status == 0) then
rewind(iunit)
do i = 1, n
read(iunit, '(a)')
end do
end if
end subroutine position_at_nml_group
现在,在读取任何(可能是可选的)名单组之前,文件首先被正确定位:
program new_reader
implicit none
character(len=40) :: line
integer :: num1, num2, num3, icode
! namelist definitions
namelist/grp1/num1
namelist/grp2/num2
namelist/grp3/num3
! open input file
open(unit=15, file='example.nml', access='sequential', &
form='formatted', status='old', iostat=icode)
read(15, '(a)') line
print *, line
call position_at_nml_group(15, 'GRP1', icode)
if (icode == 0) then
read(15, grp1)
print *, num1
end if
if (num1 == 1) then
call position_at_nml_group(15, 'GRP2', icode)
if (icode == 0) then
read(15, grp2)
print *, num2
end if
end if
call position_at_nml_group(15, 'GRP3', icode)
if (icode == 0) then
read(15, grp3)
print *, num3
end if
read(15, '(a)') line
print *, line
! close input file
close(unit=15)
contains
include 'position_at_nml_group.f90'
end program new_reader
使用这种方法消除了不同编译器如何处理在文件中的当前记录中找不到匹配的名单组的不确定性,从而为所有测试的编译器(nagfor、gfortran、ifort)生成所需的输出。
注意:为简洁起见,此处显示的代码片段中仅进行了最低限度的错误检查,可能应该添加这个(以及不区分大小写的字符串比较!)。
我正在使用最初用 Fortran 77 编写的代码,该代码使用名称列表(在编写时由编译器扩展支持 - 此功能仅在 Fortran 90 中成为标准)来读取输入文件。名单输入文件在(多个)纯文本 header 和页脚(参见 example.nml
)之间有一组名单变量。仅当先前读取的变量满足某些条件时,才会读取某些名称列表变量组。
当依次读取文件中的所有名单组时,使用 gfortran、ifort 和 nagfor 编译的可执行文件的行为都相同,并给出预期的输出。但是,当要跳过输入文件中的给定名称列表组时(可选读取),gfortran 和 ifort 可执行文件会根据需要处理此问题,而使用 nagfor 编译的可执行文件会引发运行时错误:
Runtime Error: reader.f90, line 27: Expected NAMELIST group /GRP3/ but found /GRP2/ Program terminated by I/O error on unit 15 (File="example.nml",Formatted,Sequential)
作为重现问题的最小工作示例,请考虑下面给出的名单文件 example.nml
和 driver 程序 reader.f90
,其中 NUM2
来自名单组 [=只有当 NUM1
from namelist group GRP1
等于 1
:
example.nml:
this is a header
&GRP1 NUM1=1 /
&GRP2 NUM2=2 /
&GRP3 NUM3=3 /
this is a footer
reader.f90:
program reader
implicit none
character(len=40) :: hdr, ftr
integer :: num1, num2, num3, icode
! namelist definition
namelist/grp1/num1
namelist/grp2/num2
namelist/grp3/num3
! open input file
open(unit=15, file='example.nml', form='formatted', status='old', iostat=icode)
! read input data from namelists
read(15, '(a)') hdr
print *, hdr
read(15, grp1)
print *, num1
if (num1 == 1) then
read(15, grp2)
print *, num2
end if
read(15,grp3)
print *, num3
read(15, '(a)') ftr
print *, ftr
! close input file
close(unit=15)
end program reader
当 NUM1=1
:
this is a header
1
2
3
this is a footer
然而,当例如NUM1=0
,用 gfortran 和 ifort 编译的可执行文件给出了所需的输出:
this is a header
0
3
this is a footer
当使用 nagfor(以严格符合标准而著称)编译的可执行文件时,读取 header 和第一个名单组:
this is a header
0
但随后因前面提到的运行时错误而终止。
如错误消息所示,example.nml
是按顺序访问的,如果是这种情况,/GRP2/ 是下一个要读取的记录,而不是程序逻辑要求的 /GRP3/,所以错误消息对我来说很有意义。
所以我的问题是:
- 所显示的行为是否可以归因于 nagfor 而不是 gfortran 和 ifort 强制执行的标准(非)一致性?
- 如果是这样,这是否意味着使用 gfortran 和 ifort 观察到的 non-sequential 读数是由于这些编译器(而不是 nagfor)支持的扩展?可以使用编译器标志将其 on/off 转换吗?
- 我能想到的最简单的解决方法(对现有大型程序的最小更改)是在
else
分支中为if
语句添加一个虚拟read(15,*)
reader.f90
。这似乎适用于所有提到的编译器。这会使代码符合标准(Fortran 90 或更高版本)吗?
这些是用于编译可执行文件的编译器版本和选项:
- GNU Fortran (Ubuntu 9.1.0-2ubuntu2~18.04) 9.1.0:
gfortran -Wall -Wextra -fcheck=all -g -Og -fbacktrace reader.f90
- 英特尔(R) Visual Fortran,版本 16.0 内部版本 20160415:
ifort -Od -debug:all -check:all -traceback reader.f90
- NAG Fortran Compiler Release 6.1(Tozai) Build 6116:
nagfor -O0 -g -C reader.f90
当请求对外部文件进行名单格式化时,名单记录从文件当前位置的记录开始。
名单输入记录的结构由语言规范明确定义(例如,参见 Fortran 2018 13.11.3.1)。特别是,这不允许不匹配的名单组名称。 nagfor 对此的抱怨是合理的。
几个编译器似乎确实会继续跳过记录,直到在记录中识别出名单组,但我不知道可用于控制该行为的编译器标志。从历史上看,情况通常是使用不同的文件指定多个名单。
来到你的"simple workaround":唉,这在一般情况下是不够的。名单输入可能会消耗外部文件的多个记录。 read(15,*)
只会将文件位置向前移动一条记录。您将要前进到名单的终止记录之后。
当您知道名单只是那条记录时,解决方法就很好了。
@francescalus'
本质上,解决方案是确保文件记录标记始终定位正确在尝试任何名单组读取之前。这种定位是在一个子程序中完成的,该子程序倒带输入文件,读取记录直到找到具有匹配组名的记录(如果没有找到,可以引发 error/warning),然后倒带并将文件记录标记重新定位到准备好阅读名单。
subroutine position_at_nml_group(iunit, nml_group, status)
integer, intent(in) :: iunit
character(len=*), intent(in) :: nml_group
integer, intent(out) :: status
character(len=40) :: file_str
character(len=:), allocatable :: test_str
integer :: i, n
! rewind file
rewind(iunit)
! define test string, i.e. namelist group we're looking for
test_str = '&' // trim(adjustl(nml_group))
! search for the record containing the namelist group we're looking for
n = 0
do
read(iunit, '(a)', iostat=status) file_str
if (status /= 0) then
exit ! e.g. end of file
else
if (index(adjustl(file_str), test_str) == 1) then
! backspace(iunit) ?
exit ! i.e. found record we're looking for
end if
end if
n = n + 1 ! increment record counter
end do
! can possibly replace this section with "backspace(iunit)" after a
! successful string compare, but not sure that's legal for namelist records
! thus, the following:
if (status == 0) then
rewind(iunit)
do i = 1, n
read(iunit, '(a)')
end do
end if
end subroutine position_at_nml_group
现在,在读取任何(可能是可选的)名单组之前,文件首先被正确定位:
program new_reader
implicit none
character(len=40) :: line
integer :: num1, num2, num3, icode
! namelist definitions
namelist/grp1/num1
namelist/grp2/num2
namelist/grp3/num3
! open input file
open(unit=15, file='example.nml', access='sequential', &
form='formatted', status='old', iostat=icode)
read(15, '(a)') line
print *, line
call position_at_nml_group(15, 'GRP1', icode)
if (icode == 0) then
read(15, grp1)
print *, num1
end if
if (num1 == 1) then
call position_at_nml_group(15, 'GRP2', icode)
if (icode == 0) then
read(15, grp2)
print *, num2
end if
end if
call position_at_nml_group(15, 'GRP3', icode)
if (icode == 0) then
read(15, grp3)
print *, num3
end if
read(15, '(a)') line
print *, line
! close input file
close(unit=15)
contains
include 'position_at_nml_group.f90'
end program new_reader
使用这种方法消除了不同编译器如何处理在文件中的当前记录中找不到匹配的名单组的不确定性,从而为所有测试的编译器(nagfor、gfortran、ifort)生成所需的输出。
注意:为简洁起见,此处显示的代码片段中仅进行了最低限度的错误检查,可能应该添加这个(以及不区分大小写的字符串比较!)。