读取fortran无格式文件时不一致的记录标记

时间:2013-03-25 05:45:02

标签: python fortran

我正在尝试用python读取一个非常大的Fortran无格式二进制文件。该文件包含2 ^ 30个整数。

我发现记录标记令人困惑(第一个是-2147483639),无论如何我已经实现了恢复数据结构(那些想要的整数都是相似的,因此与记录标记不同)并编写下面的代码(带有here的帮助。

但是,我们可以看到每条记录开头和结尾的标记不一样。这是为什么?

是因为数据的大小太长(536870910 =(2 ^ 30 - 4)/ 2)?  但是(2 ^ 31-1)/ 4 = 536870911> 536870910。

或者只是数据文件的作者所犯的一些错误?

另一个问题,记录,int或unsigned int开头的标记类型是什么?

fp = open(file_path, "rb")

rec_len1, = struct.unpack( '>i', fp.read(4) )
data1 = np.fromfile( fp, '>i', 536870910)
rec_end1, = struct.unpack( '>i', fp.read(4) )

rec_len2, = struct.unpack( '>i', fp.read(4) )
data2 = np.fromfile( fp, '>i', 536870910)
rec_end2, = struct.unpack( '>i', fp.read(4) )

rec_len3, = struct.unpack( '>i', fp.read(4) )
data3 = np.fromfile( fp, '>i', 4)
rec_end3, = struct.unpack( '>i', fp.read(4) )
data = np.concatenate([data1, data2, data3])

(rec_len1,rec_end1,rec_len2,rec_end2,rec_len3,rec_end3)

这里记录的lenth值如上所示:

(-2147483639, -2176, 2406, 589824, 1227787, -18)

3 个答案:

答案 0 :(得分:2)

最后,事情似乎更清楚了。

Here是英特尔Fortran编译器用户和参考指南, 请参阅记录类型:可变长度记录部分。

  

对于大于2,147,483,639字节的记录长度,记录为   分为子记录。子记录可以是从1到1的任何长度   2,147,483,639,包括在内。

     

前导长度字段的符号位表示是否记录   继续与否。尾随长度字段的符号位   表示存在前一个子记录。的位置   符号位由文件的endian格式决定。

     

继续的子记录具有带符号的前导长度字段   比特值1.构成记录的最后一个子记录有一个   符号位值为0的前导长度字段。具有的子记录   前面的子记录具有带符号位的尾随长度字段   值1.构成记录的第一个子记录有一个尾随   长度字段,符号位值为0.如果符号位的值   为1时,记录的长度存储在二进制补码表示法中。

经过多篇论文后,我意识到我被二进制补码表示法误导了,记录标记只是按照上面的规则改变符号,而是改为二进制补码表示法当符号位为1.无论如何,我的数据也可能是 用不同的编译器创建。

以下是解决方案。

数据大于2GB,因此它分为几个子记录。 我们看到第一个记录开始标记是-2147483639, 所以第一个记录的长度是2147483639,这正是子记录的最大长度,而不是我认为的2147483640,也不是2147483638的-2147483639的二进制补码表示法

如果我们跳过2147483639字节来读取记录结束标记,您将获得2147483639, 因为它是第一个结束标记为正的子记录。

以下是检查记录标记的代码:

fp = open(file_path, "rb")
while 1:
    prefix, = struct.unpack( '>i', fp.read(4) )
    fp.seek(abs(prefix), 1)    #or read |prefix| bytes data as you want
    suffix, = struct.unpack( '>i', fp.read(4) )
    print prefix, suffix
    if abs(suffix) - abs(prefix): 
        print "suffix != prefix!"
        break
    if prefix > 0: break

并进行丝网印刷

-2147483639 2147483639
-2147483639 -2147483639
18 -18

除了符号,我们可以看到记录开始标记和结束标记始终相同。 三条记录的长度分别为2147483639,2147483639,18字节,不必是4的倍数。因此第一条记录以某个整数的前3个字节结束,第二条记录以其余1字节开始。

答案 1 :(得分:0)

我发现使用f2py是python访问fortran数据的一种更方便的方法。 然而,记录标记的奇怪行为仍然是一个问题。至少我们可以避免潜入(有时令人困惑)fortran无格式文件结构。它与numpy匹配得很好。

F2PY用户指南和参考手册为here。 这是一个示例fortran源文件,用于打开和关闭文件,读取整数1-D数组和浮动2-D数组。请注意,评论以!f2py开头,它们有助于使f2py更“聪明”。

要使用它,您需要将其包装到模块中并导入到python会话中。然后你就像那些python函数一样调用这些函数。

!ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
!cc                                                         cc
!cc      FORTRAN MODULE for PYTHON PROGRAM CALLING          cc
!cc                                                         cc
!ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc

!Usage: 
!   Compile:   f2py -c fortio.f90 -m fortio
!   Import:    from fortio import *
!       or     import fortio
!Note:
!   Big endian: 1; Little endian: 0


!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE open_fortran_file(fileUnit, fileName, endian, error)
  implicit none

  character(len=256) :: fileName
  integer*4 :: fileUnit, error, endian
  !f2py integer*4 optional, intent(in) :: endian=1
  !f2py integer*4 intent(out) :: error

  if(endian .NE. 0) then
     open(unit=fileUnit, FILE=fileName, form='unformatted', status='old', &
          iostat=error, convert='big_endian')
  else
     open(unit=fileUnit, FILE=fileName, form='unformatted', status='old', &
          iostat=error)
  endif
END SUBROUTINE 

!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE read_fortran_integer4(fileUnit, arr, leng)
  implicit none

  integer*4 :: fileUnit, leng
  integer*4 :: arr(leng)
  !f2py integer*4 intent(in) :: fileUnit, leng 
  !f2py integer*4 intent(out), dimension(leng), depend(leng) :: arr(leng)

  read(fileUnit) arr
END SUBROUTINE

!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE read_fortran_real4(fileUnit, arr, row, col)
  implicit none

  integer*4 :: fileUnit, row, col
  real*4 :: arr(row,col)
  !f2py integer*4 intent(in):: fileUnit, row, col
  !f2py real*4 intent(out), dimension(row, col), depend(row, col) :: arr(row,col)

  read(fileUnit) arr
END SUBROUTINE

!cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
SUBROUTINE close_fortran_file(fileUnit, error)
  implicit none

  integer*4 :: fileUnit, error
  !f2py integer*4 intent(in) :: fileUnit
  !f2py integer*4 intent(out) :: error

  close(fileUnit, iostat=error)
END SUBROUTINE 

答案 2 :(得分:0)

由于这个问题似乎经常出现......这是一个python utilty代码,用于扫描二进制文件并确定它是否(可能是)fortran无格式的顺序访问文件。它通过尝试几种标题格式来工作。当然,由于“无格式”格式不是标准格式,因此可能存在其他变量,但这应该是最常见的格式。

请注意左括号已转义,因此您可能需要更改& #060;如果你屏幕复制这个,请回到“小于”的标志。


def scanfbinary(hformat,file,fsize):
 """ scan a file to see if it has the simple structure typical of
     an unformatted sequential access fortran binary:
     recl1,<data of length recl1 bytes>,recl1,recl2,<data of length recl2 bytes>,recl2 ...
 """
 import struct
 print 'scan type',hformat,
 if 'qQ'.find(hformat[1])>=0:  hsize=8
 elif 'iIlL'.find(hformat[1])>=0:  hsize=4
 if hformat[0] == '<':  endian='little'
 elif hformat[0] == '>':  endian='big'
 print '(',endian,'endian',hsize,'byte header)',
 f.seek(0)
 nrec = 0
 while fsize > 0:
  h0=struct.unpack(hformat,f.read(hsize))[0]
  if h0 < 0 :   print 'invalid integer ',h0; return 1
  if h0 > fsize - 2*hsize:
   print 'invalid header size ',h0,' exceeds file size ',fsize
   if nrec > 0:print 'odd perhaps a corrupe file?'
   return 2
# to read the data replace the next line with code to read h0 bytes..
# eg 
#  import numpy
#  dtype = numpy.dtype('<i')
#  record=numpy.fromfile(f,dtype,h0/dtype.itemsize) 
  f.seek(h0,1)   
  h=struct.unpack(hformat,f.read(hsize))[0]
  if h0!=h :  print 'unmatched header';   return 3
  nrec+=1
  if nrec == 1:print
  if nrec < 10:print 'read record',nrec,'size',h
  fsize-=(h+2*hsize)
 print 'successfully read ',nrec,' records with unformatted fortran header type',hformat
 return 0
f=open('binaryfilename','r')
f.seek(0,2)
fsize=f.tell()
res=[scanfbinary(hformat,f,fsize) for hformat in ('<q','>q','<i','>i')]
if res.count(0)==0:
 print 'no match found, file size ',fsize, 'starts..'
 f.seek(0)
 for i in range(0,12): print f.read(2).encode('hex_codec'),
 print