TypechoJoeTheme

MetMan's Blog

网站页面

Fortran Sequential二进制文件读入问题诊断

MetMan博 主神仙
2024-09-22
/
0 评论
/
103 阅读
/
899 个字
/
百度已收录
09/22
本文最后更新于 2024年09月22日,已超过 52天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

问题

现有一个二进制文件rain_clmn_202306.0000.dat及对应的GrADS CTL数据描述文件,使用GrADS绘图正常显示,但通过Fortran程序读入数据会报错。

CTL数据描述文件如下所示:

DSET ^rain_clmn_202306.0000.dat
OPTIONS  sequential big_endian
UNDEF   9999.000
TITLE Result for CTRL,MWR,
XDEF   45 linear     5.0000     9.0000
YDEF     8 levels     3.0000    6.0000    9.0000   12.0000   15.0000   18.0000   21.0000   24.0000
ZDEF    1 levels     0.000
TDEF   30 linear 00Z01JUN2023   1dy
VARS   12
CTRL1            1  0  CTRL (TS)
CTRL2            1  0  CTRL (MISSED)
CTRL3            1  0  CTRL (FALSEA)
CTRL4            1  0  CTRL (EFFICENT)
CTRL5            1  0  CTRL (BIAS)
CTRL6            1  0  CTRL (ETS)
MWR1             1  0  MWR (TS)
MWR2             1  0  MWR (MISSED)
MWR3             1  0  MWR (FALSEA)
MWR4             1  0  MWR (EFFICENT)
MWR5             1  0  MWR (BIAS)
MWR6             1  0  MWR (ETS)
ENDVARS

通过CTL文件可知二进制数据是Fortran sequential unformatted格式数据,其端序是大端(big_endian)。数据布局是有30个时次数据,每个时次12个变量,其中每个变量只有单层(45*8)数据。

通过下面Fortran代码读入

  integer :: ivar,itime
  real*4,dimension(45,8) :: buf
     open(99,file="rain_clmn_202306.0000.dat",form="unformatted",access="sequential",convert="big_endian")
  do itime=1,30
    do ivar=1,12
      print*, 'ivar=',ivar,'itime=',itime
      read(99) buf
    enddo
  enddo

执行报错:

 ivar=           1 itime=           1
 ivar=           2 itime=           1
forrtl: severe (24): end-of-file during read, unit 99, file rain_clmn_202306.0000.dat

以上Fortran程序完全按照CTL描述文件读取,为何在第二个记录读取时报错?

此外,使用CDO工具将该二进制文件转换成netcdf格式也能成功转换。

$ cdo -b f32 -f nc import_binary rain_clmn_202306.0000.ctl rain_00.nc
$ ncdump -h rain_00.nc

那么问题出在哪里?

Sequential unformatted数据介绍

Sequential无格式数据是Fortran标准支持的一种独有的数据格式。对于传统的基于记录的Fortran数据格式,sequential访问的无格式数据在文件中会存储记录长度。

每个记录体(Body)前后有记录头和记录尾,分别占有4个字节,存储了本记录的长度信息。读取流程大致是:Fortran运行时会先读取记录头中的记录长度信息,然后读取相应的记录数据,完成后文件指针转到下一条记录头,重复以上过程。

前面提到的CTL描述文件,每个记录长度应该是45x8x4(bytes)=1440,因此每条记录的记录头/尾应该存储整数值1440。

诊断

前面Fortran程序读取错误,我们可以将第一个记录头信息打印出来看看记录长度是多少。

integer*4 :: recl
! 这里我们使用stream格式访问 
open(99,file="rain_clmn_202306.0000.dat",form="unformatted",access="stream",convert="big_endian")
read(99) recl
print*,recl

打印出来的结果是538976288,不是我们预期的值。文件大小为521280字节,如果程序根据这个记录长度读取,在读第二个记录时已经超出文件大小了,因此程序报错end-of-file也就不足为奇了。

通过以上分析,可以判断这个sequential二进制数据有问题的。

我们可以写一个简单的Fortran转换程序生成正确的sequential二进制数据。

思路是通过流(stream)格式访问读入获取记录体数据,然后写出到sequential数据。

  integer*4 :: recl
  integer :: ivar,itime
  real*4,dimension(45,8) :: buf

! 以stream方式访问原数据    open(99,file="rain_clmn_202306.0000.dat",form="unformatted",access="stream",convert="big_endian")

 ! 生成sequential访问格式数据 open(100,file="new.dat",form="unformatted",access="sequential",convert="big_endian")

  do itime=1,30
    do ivar=1,12
      read(99) recl   ! 读入记录头,不用
      read(99) buf
      read(99) recl   ! 读入记录尾,不用
      write(100) buf  ! 写出记录体数据到sequential数据
    enddo
  enddo

我们可以验证一下新生成的数据记录头/尾记录数据是多少。

$ od --endian=big -N4 -t d4 new.dat
0000000        1440
0000004

可以看出记录头中存储了正确的记录长度信息1440。

现在还剩一个问题未解决,为何GrADS或者CDO能正确处理这个二进制文件呢?

我们来查看CDO源代码来看看是如何处理的。

CDO是由C/C++实现的,import_binary功能是在Importbinary.cc代码中实现的。

处理核心代码如下:

读取每条记录数据

size_t rc = fread(rec.data(), 1, recsize, pfi.infile);

这里的recsize长度对应前面数据应该是recsize=pfi.gsiz *4

gridsize是水平x/y维格点总数,对应以上数据是45*8

如果为sequential访问格式,记录偏移量recoffset要加4个字节,真正记录体数据从第5个字节开始提取。

char *cdata = &rec[recoffset]

因此cdo在转换二进制文件时是完全根据CTL描述文件,通过流格式读入(fread)整条记录,再截取记录体数据。不像Fortran是根据读入的记录头中记录长度信息来进一步读取记录体,也就不会遇到问题。GrADS处理方式与cdo相同。

小结

  • Fortran sequential二进制数据是基于记录格式数据,每条记录包含记录头/记录体/记录尾。
  • Fortran读取sequential二进制数据与工具CDO/GrADS(C/C++实现方式)方式不同。
fortranbinary
朗读
赞(0)
赞赏
感谢您的支持,我会继续努力哒!
版权属于:

MetMan's Blog

本文链接:

https://blog.metman.top/index.php/archives/140/(转载时请注明本文出处及文章链接)

评论 (0)

互动读者

标签云

最新回复

  1. tqymnonccc打酱油
    2024-09-27
  2. toibdpojay打酱油
    2024-09-22
  3. yvctxyevvw打酱油
    2024-09-22
  4. frezhwzwuq打酱油
    2024-09-22
登录
X
用户名
密码