Как обрабатывать необязательную группу в списке имен Fortran
Я работаю с кодом, изначально написанным на Fortran 77, который использует списки имен (поддерживаемые расширением компилятора во время его написания) для чтения входных файлов. Входные файлы списка имен содержат группы переменных списка имен между (несколькими) верхними и нижними колонтитулами обычного текста (см. example.inp
). Некоторые группы переменных списка имен читаются только при соблюдении определенных условий для ранее прочитанных переменных.
В качестве минимального рабочего примера рассмотрим входной файл списка имен example.inp
и программа для водителя reader.f90
приведен ниже. В этом примере NUM2
из группы имен GRP2
следует читать только если NUM1
из группы имен GRP1
равняется 1
:
example.inp:
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.inp', 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
Код драйвера успешно компилируется с помощью gfortran, ifort и nagfor:
- GNU Fortran (Ubuntu 9.1.0-2ubuntu2 ~ 18.04) 9.1.0:
gfortran -Wall -Wextra -pedantic -std=f95 -fcheck=all -g -Og -fbacktrace reader.f90
- Intel® Visual Fortran, версия 16.0, сборка 20160415:
ifort -Od -debug:all -check:all -traceback reader.f90
- Компилятор NAG Fortran, выпуск 6.1(Tozai), сборка 6116:
nagfor nagfor -O0 -g -C reader.f90
и все исполняемые файлы дают ожидаемый результат, когда 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 (который известен строгим соответствием стандартам), читает заголовок и первую группу имен:
this is a header
0
но затем завершается со следующей ошибкой во время выполнения:
Ошибка времени выполнения: reader.f90, строка 24: Ожидаемая группа NAMELIST /GRP3/, но найдена /GRP2/
Программа прервана из-за ошибки ввода-вывода на блоке 15 (File="example.inp", отформатированный, последовательный)
Как указано в сообщении об ошибке, example.inp
Доступ осуществляется последовательно, и в этом случае /GRP2/ является следующей записью, которая должна быть прочитана, а не /GRP3/, как требует логика программы, поэтому сообщение об ошибке имеет смысл для меня.
Итак, мой вопрос заключается в следующем:
- Может ли показанное поведение быть отнесено к стандартному (не) соответствию, обеспечиваемому nagfor, а не gfortran и ifort?
- Если это так, означает ли это, что непоследовательное чтение, наблюдаемое с gfortran и ifort, связано с расширениями, поддерживаемыми этими компиляторами (а не nagfor)? Можно ли это включить / выключить с помощью флагов компилятора?
- Самая простая работа, которую я могу придумать (минимальное изменение для большой существующей программы), это добавить пустышку
Read(15,*)
вElse
филиал дляIf
заявление вreader.f90
, Это похоже на работу со всеми упомянутыми компиляторами. Приведет ли это в соответствие стандарту кода (Fortran 90 или более поздней версии)?
1 ответ
Когда во внешнем файле запрашивается форматирование списка имен, начинается запись списка имен, начинающаяся с записи в текущей позиции файла.
Структура входной записи списка имен хорошо определена языковой спецификацией (см., Например, Fortran 2018 13.11.3.1). В частности, это не допускает несовпадения имени группы списков имен. Нагфор жаловаться на это делает это законно.
Некоторые компиляторы действительно продолжают пропускать записи до тех пор, пока в списке не будет определена группа имен, но я не знаю флагов компилятора, доступных для управления этим поведением. Исторически сложилось так, что в общем случае множественные списки имен указывались с использованием разных файлов.
Подходя к вашему "простому обходному пути": этого, увы, недостаточно в общем случае. Для ввода списка имен может потребоваться несколько записей внешнего файла. read(15,*)
продвинет позицию файла только одной записью. Вы хотите перейти к после окончания записи для списка имен.
Когда вы знаете, что список имен - это всего лишь одна запись, тогда обходной путь хорош.
@francescalus' ответ и комментарий на этот ответ, ясно объяснили, первые две части моего вопроса, указав при этом недостатке в третьей части. В надежде, что это может быть полезно для других, которые столкнутся с аналогичной проблемой с устаревшим кодом, вот обходной путь, который я реализовал:
По сути, решение состоит в том, чтобы убедиться, что маркер записи файла всегда расположен правильно, перед попыткой чтения любой группы списка имен. Это позиционирование выполняется в подпрограмме, которая перематывает входной файл, просматривает записи до тех пор, пока не найдет запись с совпадающим именем группы (если не найдено, может возникнуть ошибка / предупреждение), а затем перематывает и перемещает маркер записи файла, чтобы быть готовым. для списка имен прочтите.
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).
Примечание. Для краткости в показанном здесь фрагменте кода выполняется лишь минимальная проверка ошибок, возможно, следует добавить это (и сравнение строк без учета регистра!).