Как обрабатывать необязательную группу в списке имен 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/, как требует логика программы, поэтому сообщение об ошибке имеет смысл для меня.

Итак, мой вопрос заключается в следующем:

  1. Может ли показанное поведение быть отнесено к стандартному (не) соответствию, обеспечиваемому nagfor, а не gfortran и ifort?
  2. Если это так, означает ли это, что непоследовательное чтение, наблюдаемое с gfortran и ifort, связано с расширениями, поддерживаемыми этими компиляторами (а не nagfor)? Можно ли это включить / выключить с помощью флагов компилятора?
  3. Самая простая работа, которую я могу придумать (минимальное изменение для большой существующей программы), это добавить пустышку 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).

Примечание. Для краткости в показанном здесь фрагменте кода выполняется лишь минимальная проверка ошибок, возможно, следует добавить это (и сравнение строк без учета регистра!).

Другие вопросы по тегам