在Python中读取Fortran二进制文件

4
我在Python中读取未格式化的F77二进制文件时遇到了问题。我尝试了SciPy.io.FortraFile方法和NumPy.fromfile方法,但都没有成功。我还用IDL读取了该文件,它可以正常工作,因此我有一个数据应该是什么样子的基准。我希望有人能指出我犯了一个愚蠢的错误——没有比犯了一个傻瓜错误然后洗手不干更好的事情了……

数据bcube1具有101x101x101x3的维度,类型为r*8。总共有3090903个条目。它们使用以下语句编写(不是我的代码,而是从源代码复制的)。

open (unit=21, file=bendnm, status='new'
.     ,form='unformatted')
write (21) bcube1
close (unit=21)

我可以使用以下代码(也不是我的代码,是从同事那里复制的)在IDL中成功读取它:

bcube=dblarr(101,101,101,3)
openr,lun,'bcube.0000000',/get_lun,/f77_unformatted,/swap_if_little_endian
readu,lun,bcube
free_lun,lun

返回的数据(bcube)是双精度的,具有尺寸为101x101x101x3的维度,因此文件的标题信息知道其维度(未压缩)。

现在我尝试使用Python获得相同的效果,但没有成功。我已经尝试了以下方法。

In [30]: f = scipy.io.FortranFile('bcube.0000000', header_dtype='uint32')
In [31]: b = f.read_record(dtype='float64')

该代码返回错误:Size obtained (3092150529) is not a multiple of the dtypes given (8)。更改数据类型(dtype)可以更改得到的大小,但它仍然不能被8整除。

或者,使用fromfile没有出现错误,但返回的数组中有一个额外的值(可能是页脚),并且个别的数组值完全不正确(应该都是阶数为1)。

In [38]: f = np.fromfile('bcube.0000000')
In [39]: f.shape
Out[39]: (3090904,)
In [42]: f
Out[42]: array([ -3.09179121e-030,   4.97284231e-020,  -1.06514594e+299, ...,
         8.97359707e-029,   6.79921640e-316,  -1.79102266e-037])

我尝试使用byteswap来查看是否可以使浮点值更合理,但是并没有成功。
我认为np.fromfile方法非常接近工作状态,但读取头信息的方式可能有问题。有人能否建议我如何找出应该在头文件中的内容,以便IDL知道数组的维度和数据类型?是否有一种方法可以传递头信息给fromfile,以便它知道如何处理前导条目?

1
你有看过例如 https://dev59.com/fZffa4cB1Zd3GeqP9o-9 (通过谷歌找到;Python读取Fortran二进制文件)吗? - albert
请在所有Fortran问题中使用标签[tag:fortran]。无论如何,您的问题都不是特定于版本的。 - Vladimir F Героям слава
@VladimirF,如果我的问题不够清晰,我很抱歉。也许我可以重新表述一下。为什么np.fromfile(fname)返回的值比数组中的值多?在我的情况下,应该有3090903个条目,但结果有3090904个条目。为什么它返回的值与源数组中的值不相等? - NoMansEyes
@albert 是的,我看过那篇帖子。它解决了数组内容数据类型错误的问题。然而,我知道数组中的数据是r8,所以我知道Python数据类型应该是float64。 - NoMansEyes
不用担心,我只是在提到标签fortran vs标签fortran77,并没有指你问题的清晰度。 - Vladimir F Героям слава
我不了解Fortran。但是对于np.fromfile,它会读取没有任何头部或尾部的原始数据。如果您的文件有头部(并且希望您知道它有多长),我相信您可以通过将一个已经使用openseek打开的文件传递给np.fromfile来跳过头部。如果您的文件有尾部,请使用np.fromfilecount参数。如果您不确定头部/尾部的长度,请使用0.进行实验,并使用十六进制编辑器观察文件。 - ZisIsNotZis
2个回答

3

我对此进行了一些尝试,我觉得我有一个想法。

Fortran存储未格式化的数据并没有标准化,所以你需要进行一些尝试,但你需要三个信息:

  1. 数据格式。你建议使用64位实数,或在Python中使用“f8”。
  2. 头文件类型。这是一个无符号整数,但你需要字节长度。如果不确定,请尝试4个字节。

    头文件通常存储记录的字节长度,并在末尾重复存储。

    不过,由于它并没有标准化,所以不能保证。

  3. 大小端模式,小端或大端。

    技术上来说,对于头文件和值都是如此,但我假设它们是相同的。

    Python默认为小端模式,因此如果该设置适用于您的数据,则认为您已经解决了它。

当你使用scipy.io.FortranFile打开文件时,你需要给出header的数据类型。因此,如果数据存储为big_endian,并且你有一个4字节的无符号整数头文件,你需要这样操作:

from scipy.io import FortranFile
ff = FortranFile('data.dat', 'r', '>u4')

当你读取数据时,需要知道数值的数据类型。假设是big_endian,你需要使用>f8类型:

vals = ff.read_reals('>f8')

点击这里查看数据类型的语法描述。

如果您可以控制编写数据的程序,我强烈建议您将它们写入数据流中,这样Python可以更轻松地读取。


1
搞定了!非常感谢!对于任何未来的读者,解决方案是使用由Array接口定义的数据类型。简而言之,'>u4'和'>f8' 意味着 bigendian 'uint32'和'float64',但是它们仅适用于C/F API。 - NoMansEyes

0

Fortran有记录分界符,即使在二进制文件中也缺乏文档。

因此,对于未格式化的文件的每次写入:

integer*4 Test1
real*4 Matrix(3,3)

open(78,format='unformatted')
write(78) Test1
write(78) Matrix
close(78)

最终应该由np.int32值填充。(我看到过参考资料,这告诉你记录长度,但我个人没有验证过。)

可以通过numpy在Python中读取上述内容:

input_file = open(file_location,'rb')
datum = np.dtype([('P1',np.int32),('Test1',np.int32),('P2',np.int32),('P3',mp.int32),('MatrixT',(np.float32,(3,3))),('P4',np.int32)])
data = np.fromfile(input_file,datum)

这将完全填充数据数组,其中包含上述格式的各个数据集。请注意,numpy希望数据以C格式(行主要)打包,而Fortran格式数据是列主要的。对于像上面那样的方阵形状,这意味着在使用之前需要进行转置才能从矩阵中获取数据。对于非方阵,您需要重新调整形状和转置:

Matrix = np.transpose(data[0]['MatrixT']

转置您的4-D数据结构需要仔细处理。您可以考虑使用SciPy自动完成此操作;SciPy软件包似乎具有Fortran相关实用程序,我尚未完全探索。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接