ODC库使用:指定列对应数组
这应该是ODC库使用教程最后一篇文章了。不过在后面分享Fortran与C互操作(混编)时可能会以ODC库实现方式为例介绍相关内容。
前面介绍的ODC编码方式是将不同类型的所有列数据通过transfer
函数转换成双精度(real*8
)浮点类型,同时将所有列数据组织成一个二维表格形式,放入一个二维数组中再进行解编码处理。
可能很多人不习惯这种使用方式,更希望通过指定每列对应的(一维)数组buffers方式进行操作。这样做的好处是避免了显式调用transfer
数据类型转换。
下面介绍ODC这种使用方式。
指定列数组编码
查看官方实例:https://odc.readthedocs.io/en/latest/content/usage-examples/encode-custom.html
首先使用add_column
方法添加表格列信息
! Define all column names and their types
call check_call(encoder%add_column('expver', ODC_STRING), 'adding expver column')
call check_call(encoder%add_column('date@hdr', ODC_INTEGER), 'adding date@hdr column')
...
然后定义数据布局,并绑定每列对应的数据数组(关键部分)
! Set a custom data layout and data array for each column
call check_call(encoder%column_set_data_array(1, 8, stride=8, data=c_loc(data1)), 'setting expver array')
call check_call(encoder%column_set_data_array(2, 8, stride=8, data=c_loc(data2)), 'setting date array')
...
其中encoder对象方法column_set_data_array
接口说明如下
- 第一个col参数指定设置的列索引位置
- 第二个参数
element_size
指定该列宽度,单位是字节数,如果想用以双精度大小(8 bytes)为单位,可以使用第三个element_size_doubles
参数。 stride
指定列宽度,单位是字节数- 最后一个
data
参数指定绑定的数组,接口要求是type(c_ptr)
类型,即C指针。
我们先看下data1,data2,...
这些数组声明:
character(8), target :: data1(nrows)
integer(8), target :: data2(nrows)
...
可以看出数组类型与表格列指定(add_column()
)的数据类型是一致的。
同时它们都必须具有target
属性,因为在绑定数组时要求data参数是C指针类型(type(c_ptr)
)。通过iso_c_binding
内置模块函数c_loc
获取Fortran数组的C指针地址。
data = c_loc(data1)
如果去掉target
属性,编译应该会报类似如下错误:
custom_encoding.f90(74): error #9022: The argument to C_LOC must be a variable with the POINTER or TARGET attribute. [DATA1]
call check_call(encoder%column_set_data_array(1, 8, stride=8, data=c_loc(data1)), 'setting expver array')
官方示例中使用全局数组并显式指定数组大小,但真实应用中常通过从观测源数据获取本次处理的实际观测数。所以,一般声明数据数组为动态数组类型。
比如
character(8), allocatable,target :: data1(:)
...
allocate(data1(nrows))
注意alloctable动态数组的target
属性必须存在。
如果使用pointer
属性,则不需要target
属性。
character(8), pointer :: data1(:)
表格每列绑定好对应数组后调用encoder%encode
进行ODB文件写操作。
指定列数组解码
同样的,decoder对象中也提供了指定列数组的解码操作,这里不再详述。
下面提供一个简单示例供参考。
program odc_custom_decoding
use, intrinsic :: iso_c_binding, only: c_loc,c_null_char,c_ptr,c_f_pointer
use odc
implicit none
logical,parameter :: aggregated = .true.
integer, parameter :: max_aggregated_rows = 100000
character(255) :: path
integer(8) :: nrows
integer :: ncols
character(8), allocatable, target :: data1(:)
integer(8), allocatable, target :: data2(:)
character(8), allocatable, target :: data3(:)
character(16), allocatable, target :: data4(:)
real(8), allocatable, target :: data5(:)
integer(8), allocatable, target :: data6(:)
real(8), allocatable, target :: data7(:)
integer(8), allocatable, target :: data8(:)
type(odc_reader) :: reader
type(odc_frame) :: frame
type(odc_decoder) :: decoder
integer(8) :: rows_decoded
integer :: ele_size, istride
integer :: i
if (command_argument_count() /= 1) then
call usage()
stop 1
end if
! Get output path from command argument
call get_command_argument(1, path)
! Initialise API and set treatment of integers as longs
call check_call(odc_initialise_api(), 'initialising api')
call check_call(odc_integer_behaviour(ODC_INTEGERS_AS_LONGS), 'setting integer behaviour to longs')
call check_call(reader%open_path(path), 'open odb input file')
call check_call(frame%initialise(reader), 'initialise odb frame')
call check_call(frame%next(aggregated, max_aggregated_rows), 'enter into next frame')
call check_call(frame%row_count(nrows), "get nrows info")
call check_call(frame%column_count(ncols), "get ncols info")
print* , 'nrows =',nrows, 'ncols =', ncols
call check_call(decoder%initialise(), 'initialise decoder')
call check_call(decoder%defaults_from_frame(frame), 'get decoder info from frame')
allocate(data2(nrows))
allocate(data3(nrows))
allocate(data5(nrows))
call check_call(decoder%column_set_data_array(2, 8, stride=8, data=c_loc(data2)), 'bind data2 into column 1')
call check_call(decoder%column_set_data_array(3, 8, stride=8, data=c_loc(data3)), 'bind data3 into column 1')
call check_call(decoder%column_set_data_array(5, 8, stride=8, data=c_loc(data5)), 'bind data5 into column 1')
call check_call(decoder%decode(frame, rows_decoded), 'decode data')
do i=1,nrows
write(*,*) i, data2(i),data3(i),data5(i)
enddo
call check_call(decoder%free(), 'free decoder')
call check_call(frame%free(), 'free odb frame')
call check_call(reader%close(), 'close odb input file')
contains
subroutine check_call(err, desc)
integer, intent(in) :: err
character(*), intent(in) :: desc
if (err /= ODC_SUCCESS) then
write(7, *) '**** An error occurred in ODC library'
write(7, *) 'Description: ', desc
write(7, *) 'Error: ', odc_error_string(err)
stop 1
end if
end subroutine
subroutine usage()
write(6, *) 'Usage:'
write(6, *) ' odc-fortran-decode-custom <odb2 input file>'
end subroutine
end program
小结
相比原先整个表格绑定一个二维数组方式,本文介绍的每一列绑定一个对应一维数组方式有如下优点:
- 使用更加灵活,可以只解码部分列信息;
- 避免调用
transfer
进行数据类型的转换。

