Skip to content

Adds methods for iterating and visiting #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,32 @@ class(*), intent(out) :: attrval(:) !< character, real, integer
call h%delete_attr(dname, attr)
```

## Iterate over all datasets in a group

```fortran
call h%iterate(group, callback)

character(*), intent(in) :: group
subroutine callback(group_name, object_name, object_type)
character(len=*), intent(in) :: group_name
character(len=*), intent(in) :: object_name
character(len=*), intent(in) :: object_type
end subroutine
```

## Visit recursively all datasets starting from a group

```fortran
call h%visit(group, callback)

character(*), intent(in) :: group
subroutine callback(group_name, object_name, object_type)
character(len=*), intent(in) :: group_name
character(len=*), intent(in) :: object_name
character(len=*), intent(in) :: object_type
end subroutine
```

## high level operations

These are single-call operations that are slower than the object-oriented methods above.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ On HPC, we suggest using the HDF5 library provided by the HPC system for best pe
* variable length dataset writing

We didn't use `type(c_ptr)` and `c_loc()` internally for datasets as we observed problems when the actual argument is sliced on read/write.
The current h5fortran impementation (Fortran `select type` for H5Dwrite/H5Dread) does work with sliced actual arguments.
The current h5fortran implementation (Fortran `select type` for H5Dwrite/H5Dread) does work with sliced actual arguments.

HDF5 Fortran 2003
[features](https://docs.hdfgroup.org/archive/support/HDF5/doc/fortran/NewFeatures_F2003.pdf)
Expand Down
17 changes: 11 additions & 6 deletions fpm.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ auto-tests = false
auto-examples = false
external-modules = ["hdf5", "h5lt"]

# HDF5 >= 1.10.6
# link = ["hdf5_hl_fortran", "hdf5_fortran", "hdf5_hl", "hdf5"]

# HDF5 < 1.10.6, and distros that rename the old way such as Ubuntu 22.04
link = ["hdf5hl_fortran", "hdf5_fortran", "hdf5_hl", "hdf5"]

[install]
library = true

Expand Down Expand Up @@ -77,6 +71,17 @@ main = "test_shape.f90"
name = "string"
main = "test_string.f90"

[[test]]
name = "visit"
main = "test_visit.f90"

[[test]]
name = "iterate"
main = "test_iterate.f90"

[[test]]
name = "version"
main = "test_version.f90"

[dependencies]
hdf5 = '*'
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
set(s ${CMAKE_CURRENT_SOURCE_DIR})

target_sources(h5fortran PRIVATE
${s}/interface.f90
${s}/utils.f90
${s}/read.f90 ${s}/read_scalar.f90 ${s}/read_ascii.f90 ${s}/reader.f90
${s}/write.f90 ${s}/write_scalar.f90 ${s}/writer.f90
${s}/reader_lt.f90 ${s}/writer_lt.f90
${s}/interface.f90
${s}/attr.f90
${s}/attr_read.f90
${s}/attr_write.f90
${s}/iterate.f90
${s}/visit.f90
)
56 changes: 56 additions & 0 deletions src/interface.f90
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ module h5fortran
procedure, public :: is_open
procedure, public :: delete_attr => attr_delete
procedure, public :: exist_attr => attr_exist
procedure, public :: iterate => hdf_iterate
procedure, public :: visit => hdf_visit
!! procedures without mapping

!> below are procedure that need generic mapping (type or rank agnostic)
Expand Down Expand Up @@ -638,6 +640,60 @@ module logical function attr_exist(self, obj_name, attr_name)
character(*), intent(in) :: obj_name, attr_name
end function

module subroutine hdf_iterate(self, group_name, callback)
!! Opens the HDF5 file and the specified group, then iterates over
!! all members of the group. For each member the user‐provided
!! callback is invoked with:
!!
!! self - the HDF5 file object
!! group_name - name of the group
!! object_name - name of the member object
!! object_type - a short string indicating type ("group", "dataset",
!! "datatype", or "other")
class(hdf5_file), intent(in) :: self
character(len=*), intent(in) :: group_name
interface
subroutine user_callback_interface(group_name, object_name, object_type)
character(len=*), intent(in) :: group_name
!! The name of the group being traversed.
character(len=*), intent(in) :: object_name
!! The name of the object encountered.
character(len=*), intent(in) :: object_type
!!A short description such as "group", "dataset",
!! "datatype", or "other"
end subroutine
end interface

procedure(user_callback_interface) :: callback
end subroutine

module subroutine hdf_visit(self, group_name, callback)
!! Opens the HDF5 file and the specified group, then visits recursively
!! all members of the group. For each member the user‐provided
!! callback is invoked with:
!!
!! self - the HDF5 file object
!! group_name - name of the group
!! object_name - name of the member object
!! object_type - a short string indicating type ("group", "dataset",
!! "datatype", or "other")
class(hdf5_file), intent(in) :: self
character(len=*), intent(in) :: group_name
interface
subroutine user_callback_interface(group_name, object_name, object_type)
character(len=*), intent(in) :: group_name
!! The name of the group being traversed.
character(len=*), intent(in) :: object_name
!! The name of the object encountered.
character(len=*), intent(in) :: object_type
!!A short description such as "group", "dataset",
!! "datatype", or "other"
end subroutine
end interface

procedure(user_callback_interface) :: callback
end subroutine

end interface


Expand Down
112 changes: 112 additions & 0 deletions src/iterate.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
submodule (h5fortran) iterate_smod
use h5fortran
use hdf5
implicit none

interface
subroutine user_callback_interface(group_name, object_name, object_type)
character(len=*), intent(in) :: group_name
!! The name of the group being traversed.
character(len=*), intent(in) :: object_name
!! The name of the object encountered.
character(len=*), intent(in) :: object_type
!!A short description such as "group", "dataset",
!! "datatype", or "other"
end subroutine
end interface

type :: iterate_data_t
procedure(user_callback_interface), nopass, pointer :: callback => null()
end type iterate_data_t

contains

module procedure hdf_iterate
use, intrinsic :: iso_c_binding, only: c_funptr, C_NULL_PTR, c_int
implicit none
integer(hid_t) :: group_id
integer(c_int) :: status
integer(hsize_t) :: idx
type(c_funptr) :: funptr
type(c_ptr) :: op_data_ptr
integer(c_int) :: return_value

type(iterate_data_t) :: data

! Fill the iteration data with the user’s group name and callback.
data % callback => callback

! Open the group.
call H5Gopen_f(self%file_id, trim(group_name), group_id, status)
call estop(status, "hdf_iterate:H5Gopen_f", self%filename, "Error opening group: " // trim(group_name))

idx = 0
op_data_ptr = C_NULL_PTR
! Get the C function pointer for our internal callback.
funptr = c_funloc(internal_iterate_callback)

! Call H5Literate_f to iterate over the group.
call H5Literate_f(group_id, H5_INDEX_NAME_F, H5_ITER_NATIVE_F, idx, &
funptr, op_data_ptr, return_value, status)
call estop(status, "hdf_iterate:H5Literate_f", self%filename, "Error during iteration of group: " // trim(group_name))

! Close the group and file.
call H5Gclose_f(group_id, status)

contains

integer(c_int) function internal_iterate_callback(grp_id, name, info, op_data) bind(C)
!! internal_iterate_callback:
!!
!! This is the callback procedure that will be passed to H5Literate_f.
!! It matches HDF5’s expected signature (using bind(C)) and is called
!! for each object in the group.
!!
!! It extracts the object name from the provided character array,
!! calls H5Oget_info_by_name_f to determine the object type, and then
!! calls the user's callback with the high-level parameters.
use ISO_C_BINDING, only: c_int, c_ptr, c_null_char
implicit none
integer(c_long), value :: grp_id
character(kind=c_char, len=1) :: name(0:255)
type(h5l_info_t) :: info
type(c_ptr) :: op_data

integer :: status, i, len
type(H5O_info_t) :: infobuf
character(len=256) :: name_string
character(:), allocatable :: object_type

! Build a Fortran string from the character array.
do i = 0, 255
len = i
if (name(i) == c_null_char) exit
name_string(i+1:i+1) = name(i)(1:1)
end do

! Retrieve object info using the object name.
call H5Oget_info_by_name_f(grp_id, name_string(1:len), infobuf, status)
if (status /= 0) then
internal_iterate_callback = status
return
end if

if(infobuf % type == H5O_TYPE_GROUP_F)then
object_type = "group"
else if(infobuf % type == H5O_TYPE_DATASET_F)then
object_type = "dataset"
else if(infobuf % type == H5O_TYPE_NAMED_DATATYPE_F)then
object_type = "datatype"
else
object_type = "other"
endif

! Call the user’s callback procedure.
call data % callback(group_name, name_string(1:len), object_type)

internal_iterate_callback = 0 ! Indicate success.
end function internal_iterate_callback

end procedure hdf_iterate

end submodule
5 changes: 3 additions & 2 deletions src/utils.f90
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,9 @@
write(stderr,*) "ERROR:h5fortran:get_slice: memory size /= dataset size: check variable slice (index). " // &
" Dset_dims:", ddims, "C Mem_dims:", c_mem_dims, "mem_dims:", mem_dims, "rank(mem_dims):", rank(mem_dims)
error stop "ERROR:h5fortran:get_slice " // dset_name
elseif(any(iend-1 > ddims)) then
write(stderr,*) "ERROR:h5fortran:get_slice: iend: ", iend, ' > dset_dims: ', ddims
elseif(any(iend-istart > ddims)) then
write(stderr,*) "ERROR:h5fortran:get_slice: slice bigger than dataset: check variable slice (index). " // &
" Dset_dims:", ddims, "C Mem_dims:", c_mem_dims, "mem_dims:", mem_dims, "rank(mem_dims):", rank(mem_dims)
error stop "ERROR:h5fortran:get_slice " // dset_name
endif

Expand Down
112 changes: 112 additions & 0 deletions src/visit.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
submodule (h5fortran) visit_smod
use h5fortran
use hdf5
implicit none

interface
subroutine user_callback_interface(group_name, object_name, object_type)
character(len=*), intent(in) :: group_name
!! The name of the group being traversed.
character(len=*), intent(in) :: object_name
!! The name of the object encountered.
character(len=*), intent(in) :: object_type
!!A short description such as "group", "dataset",
!! "datatype", or "other"
end subroutine
end interface

type :: visit_data_t
procedure(user_callback_interface), nopass, pointer :: callback => null()
end type visit_data_t

contains

module procedure hdf_visit
use, intrinsic :: iso_c_binding, only: c_funptr, C_NULL_PTR, c_int
implicit none
integer(hid_t) :: group_id
integer(c_int) :: status
integer(hsize_t) :: idx
type(c_funptr) :: funptr
type(c_ptr) :: op_data_ptr
integer(c_int) :: return_value

type(visit_data_t) :: data

! Fill the iteration data with the user’s group name and callback.
data % callback => callback

! Open the group.
call H5Gopen_f(self%file_id, trim(group_name), group_id, status)
call estop(status, "hdf_visit:H5Gopen_f", self%filename, "Error opening group: " // trim(group_name))

idx = 0
op_data_ptr = C_NULL_PTR
! Get the C function pointer for our internal callback.
funptr = c_funloc(internal_visit_callback)

! Call H5Lvisit_f to visit over the group.
call H5Ovisit_f(group_id, H5_INDEX_NAME_F, H5_ITER_NATIVE_F, &
funptr, op_data_ptr, return_value, status)
call estop(status, "hdf_visit:H5Lvisit_f", self%filename, "Error during iteration of group: " // trim(group_name))

! Close the group and file.
call H5Gclose_f(group_id, status)

contains

integer(c_int) function internal_visit_callback(grp_id, name, info, op_data) bind(C)
!! internal_visit_callback:
!!
!! This is the callback procedure that will be passed to H5Lvisit_f.
!! It matches HDF5’s expected signature (using bind(C)) and is called
!! for each object in the group.
!!
!! It extracts the object name from the provided character array,
!! calls H5Oget_info_by_name_f to determine the object type, and then
!! calls the user's callback with the high-level parameters.
use ISO_C_BINDING, only: c_int, c_ptr, c_null_char
implicit none
integer(c_long), value :: grp_id
character(kind=c_char, len=1) :: name(0:255)
type(h5l_info_t) :: info
type(c_ptr) :: op_data

integer :: status, i, len
type(H5O_info_t) :: infobuf
character(len=256) :: name_string
character(:), allocatable :: object_type

! Build a Fortran string from the character array.
do i = 0, 255
len = i
if (name(i) == c_null_char) exit
name_string(i+1:i+1) = name(i)(1:1)
end do

! Retrieve object info using the object name.
call H5Oget_info_by_name_f(grp_id, name_string(1:len), infobuf, status)
if (status /= 0) then
internal_visit_callback = status
return
end if

if(infobuf % type == H5O_TYPE_GROUP_F)then
object_type = "group"
else if(infobuf % type == H5O_TYPE_DATASET_F)then
object_type = "dataset"
else if(infobuf % type == H5O_TYPE_NAMED_DATATYPE_F)then
object_type = "datatype"
else
object_type = "other"
endif

! Call the user’s callback procedure.
call data % callback(group_name, name_string(1:len), object_type)

internal_visit_callback = 0 ! Indicate success.
end function internal_visit_callback

end procedure hdf_visit

end submodule
Loading