Skip to content

Commit 409f94a

Browse files
committed
add read/write attributes
1 parent 60e6168 commit 409f94a

File tree

5 files changed

+263
-23
lines changed

5 files changed

+263
-23
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ configure_file(hdf5_config.in.f90 hdf5_config.f90 @ONLY)
55

66
# main library
77
add_library(h5mpi interface.f90
8+
attributes.f90
89
${CMAKE_CURRENT_BINARY_DIR}/hdf5_config.f90
910
)
1011
target_link_libraries(h5mpi PRIVATE HDF5::HDF5 MPI::MPI_Fortran)

src/attributes.f90

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
submodule (h5mpi) attributes
2+
3+
use h5lt, only: h5ltset_attribute_string_f, h5ltset_attribute_float_f, h5ltset_attribute_double_f, h5ltset_attribute_int_f, &
4+
h5ltget_attribute_string_f, h5ltget_attribute_float_f, h5ltget_attribute_double_f, h5ltget_attribute_int_f, &
5+
h5ltget_attribute_ndims_f, h5ltget_attribute_info_f
6+
7+
implicit none (type, external)
8+
9+
10+
contains
11+
12+
13+
module procedure readattr_char
14+
!! NOTE: HDF5 character attributes are scalar.
15+
integer :: ier
16+
character(len(attrval)) :: buf
17+
18+
if(.not.self%is_open()) error stop 'h5fortran:readattr: file handle is not open'
19+
20+
call h5ltget_attribute_string_f(self%file_id, dname, attr, buf, ier)
21+
if (ier /= 0) error stop "ERROR:h5fortran:readattr_char: problem reading attr of " // dname // " in " // self%filename
22+
23+
attrval = buf
24+
25+
end procedure readattr_char
26+
27+
28+
module procedure readattr_num
29+
!! NOTE: HDF5 has 1D vector attributes for integer, float and double.
30+
integer :: ier
31+
32+
if(.not.self%is_open()) error stop 'h5fortran:readattr: file handle is not open'
33+
34+
call attr_shape_check(self, dname, attr, size(attrval), ier)
35+
if (ier /= 0) error stop "ERROR:h5fortran:readattr_num: shape check attr of " // dname // " in " // self%filename
36+
37+
select type(attrval)
38+
type is (real(real32))
39+
call h5ltget_attribute_float_f(self%file_id, dname, attr, attrval, ier)
40+
type is (real(real64))
41+
call h5ltget_attribute_double_f(self%file_id, dname, attr, attrval, ier)
42+
type is (integer(int32))
43+
call h5ltget_attribute_int_f(self%file_id, dname, attr, attrval, ier)
44+
class default
45+
error stop "ERROR:h5fortran:readattr_num: unknown dataset type for " // dname // " in " // self%filename
46+
end select
47+
48+
if (ier /= 0) error stop "ERROR:h5fortran:readattr_num: " // dname // " in " // self%filename
49+
50+
end procedure readattr_num
51+
52+
53+
module procedure writeattr_char
54+
!! NOTE: HDF5 character attributes are scalar.
55+
56+
integer :: ier
57+
58+
if(.not.self%is_open()) error stop 'ERROR:h5fortran:writeattr: file handle is not open'
59+
60+
call h5ltset_attribute_string_f(self%file_id, dname, attr, attrval, ier)
61+
62+
if (ier /= 0) error stop "ERROR:h5fortran:writeattr_char: " // dname // " in " // self%filename
63+
64+
end procedure writeattr_char
65+
66+
67+
module procedure writeattr_num
68+
!! NOTE: HDF5 has 1D vector attributes for integer, float and double.
69+
integer :: ier
70+
integer(size_t) :: dsize
71+
72+
if(.not.self%is_open()) error stop 'ERROR:h5fortran:writeattr: file handle is not open'
73+
74+
dsize = size(attrval)
75+
76+
select type(attrval)
77+
type is (real(real32))
78+
call h5ltset_attribute_float_f(self%file_id, dname, attr, attrval, dsize, ier)
79+
type is (real(real64))
80+
call h5ltset_attribute_double_f(self%file_id, dname, attr, attrval, dsize, ier)
81+
type is (integer(int32))
82+
call h5ltset_attribute_int_f(self%file_id, dname, attr, attrval, dsize, ier)
83+
class default
84+
error stop "ERROR:h5fortran:writeattr_num: unknown dataset type for " // dname // " in " // self%filename
85+
end select
86+
87+
if (ier /= 0) error stop "ERROR:h5fortran:writeattr_num: " // dname // " in " // self%filename
88+
89+
end procedure writeattr_num
90+
91+
92+
subroutine attr_shape_check(self, dname, attr, asize, ierr)
93+
class(hdf5_file), intent(in) :: self
94+
character(*), intent(in) :: dname, attr
95+
integer, intent(in) :: asize
96+
integer, intent(out) :: ierr
97+
98+
integer :: arank, atype
99+
integer(size_t) :: attr_bytes
100+
integer(hsize_t) :: adims(1)
101+
102+
if(.not. self%is_open()) error stop 'h5fortran:attr_shape: file handle is not open'
103+
104+
if (.not. self%exist(dname)) then
105+
error stop 'ERROR:h5fortran ' // dname // ' attribute ' // attr // ' does not exist in ' // self%filename
106+
endif
107+
108+
!> check for matching rank, else bad reads can occur--doesn't always crash without this check
109+
call h5ltget_attribute_ndims_f(self%file_id, dname, attr, arank, ierr)
110+
if (ierr /= 0) error stop 'ERROR:h5fortran:get_attribute_ndims: ' // dname // ' ' // self%filename
111+
112+
if (arank /= 1) then
113+
write(stderr,'(A,I6,A,I6)') 'ERROR: attribute rank mismatch ' // dname // ' attribute "' // attr // '" = ', &
114+
arank,' should be 1'
115+
error stop
116+
endif
117+
118+
!> check for matching size, else bad reads can occur.
119+
120+
call h5ltget_attribute_info_f(self%file_id, dname, attr, adims, atype, attr_bytes, ierr)
121+
if (ierr /= 0) error stop 'ERROR:h5fortran: get_attribute_info' // dname // ' read ' // self%filename
122+
123+
if(.not. all(asize == adims)) then
124+
write(stderr,*) 'ERROR:h5fortran: shape mismatch ' // dname // ' attribute "' // attr //'" = ', adims,' shape =', asize
125+
error stop
126+
endif
127+
128+
end subroutine attr_shape_check
129+
130+
131+
end submodule attributes

src/interface.f90

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ module h5mpi
6565
generic, public :: read => h5read_scalar, ph5read_1d, ph5read_2d, ph5read_3d, ph5read_4d, ph5read_5d, ph5read_6d, ph5read_7d
6666
!! mapped procedures
6767

68-
procedure,private :: h5write_scalar, ph5write_1d, ph5write_2d, ph5write_3d, ph5write_4d, ph5write_5d, ph5write_6d, ph5write_7d
68+
!> write attributes
69+
generic, public :: writeattr => writeattr_char, writeattr_num
70+
71+
!> read attributes
72+
generic, public :: readattr => readattr_char, readattr_num
73+
74+
procedure, private :: writeattr_char, writeattr_num, readattr_char, readattr_num
75+
76+
procedure, private :: h5write_scalar, ph5write_1d, ph5write_2d, ph5write_3d, ph5write_4d, ph5write_5d, ph5write_6d, ph5write_7d
6977

7078
procedure, private :: h5read_scalar, ph5read_1d, ph5read_2d, ph5read_3d, ph5read_4d, ph5read_5d, ph5read_6d, ph5read_7d
7179
!! mapped procedures must be declared again like this
@@ -84,7 +92,7 @@ module h5mpi
8492

8593
private
8694
public :: mpi_h5comm, hdf5_file, mpi_tags, has_parallel_compression, is_hdf5, &
87-
check, hdf_rank_check, hdf_shape_check, mpi_collective, mpi_hyperslab, &
95+
hdf_rank_check, hdf_shape_check, mpi_collective, mpi_hyperslab, &
8896
hdf5version, h5exist, &
8997
HSIZE_T, &
9098
H5T_INTEGER_F, H5T_FLOAT_F, H5T_STRING_F, &
@@ -324,6 +332,37 @@ end subroutine ph5read_7d
324332

325333
end interface
326334

335+
336+
interface !< attributes.f90
337+
338+
module subroutine readattr_char(self, dname, attr, attrval)
339+
class(hdf5_file), intent(in) :: self
340+
character(*), intent(in) :: dname, attr
341+
character(*), intent(inout) :: attrval
342+
!! intent(inout) for character
343+
end subroutine readattr_char
344+
345+
module subroutine readattr_num(self, dname, attr, attrval)
346+
class(hdf5_file), intent(in) :: self
347+
character(*), intent(in) :: dname, attr
348+
class(*), intent(out) :: attrval(:)
349+
end subroutine readattr_num
350+
351+
module subroutine writeattr_char(self, dname, attr, attrval)
352+
class(hdf5_file), intent(in) :: self
353+
character(*), intent(in) :: dname, attr
354+
character(*), intent(in) :: attrval
355+
end subroutine writeattr_char
356+
357+
module subroutine writeattr_num(self, dname, attr, attrval)
358+
class(hdf5_file), intent(in) :: self
359+
character(*), intent(in) :: dname, attr
360+
class(*), intent(in) :: attrval(:)
361+
end subroutine writeattr_num
362+
363+
end interface
364+
365+
327366
contains
328367

329368

@@ -731,26 +770,6 @@ subroutine hdf_flush(self)
731770
end subroutine hdf_flush
732771

733772

734-
logical function check(ierr, filename, dname)
735-
integer, intent(in) :: ierr
736-
character(*), intent(in), optional :: filename, dname
737-
738-
character(:), allocatable :: fn, dn
739-
740-
check = .false.
741-
if (ierr==0) return
742-
743-
check = .true.
744-
fn = ""
745-
dn = ""
746-
if (present(filename)) fn = filename
747-
if (present(dname)) dn = dname
748-
749-
write(stderr,*) 'ERROR: ' // fn // ':' // dn // ' error code ', ierr
750-
751-
end function check
752-
753-
754773
logical function hdf_exist(self, dname) result(exists)
755774
class(hdf5_file), intent(in) :: self
756775
character(*), intent(in) :: dname

src/tests/unit/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ add_library(test_utils OBJECT utils.f90)
2020

2121
# --- unit tests
2222

23-
set(test_names cast deflate_write deflate_props deflate_read destructor exist groups layout shape string)
23+
set(test_names attributes cast deflate_write deflate_props deflate_read destructor exist groups layout shape string)
2424

2525
foreach(t IN LISTS test_names)
2626

src/tests/unit/test_attributes.f90

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
program test_attributes
2+
3+
use, intrinsic:: iso_fortran_env, only: int32, real32, real64, stderr=>error_unit
4+
5+
use h5mpi, only: hdf5_file
6+
use mpi, only : mpi_init, MPI_COMM_WORLD, mpi_comm_rank
7+
8+
implicit none (type, external)
9+
10+
external :: mpi_finalize
11+
12+
character(*), parameter :: filename = 'test_attr.h5'
13+
14+
integer :: ierr, mpi_id
15+
16+
call mpi_init(ierr)
17+
if (ierr /= 0) error stop "mpi_init"
18+
19+
call mpi_comm_rank(MPI_COMM_WORLD, mpi_id, ierr)
20+
if (ierr /= 0) error stop "mpi_comm_rank"
21+
22+
23+
call test_write_attributes(filename)
24+
if(mpi_id == 0) print *,'PASSED: HDF5 write attributes'
25+
26+
call test_read_attributes(filename)
27+
if(mpi_id == 0) print *, 'PASSED: HDF5 read attributes'
28+
29+
30+
call mpi_finalize(ierr)
31+
if (ierr /= 0) error stop "mpi_finalize"
32+
33+
34+
contains
35+
36+
37+
subroutine test_write_attributes(path)
38+
39+
type(hdf5_file) :: h
40+
character(*), intent(in) :: path
41+
42+
call h%open(path, action='w', mpi=.true.)
43+
44+
call h%write('/x', 1)
45+
46+
call h%writeattr('/x', 'note','this is just a little number')
47+
call h%writeattr('/x', 'hello', 'hi')
48+
call h%writeattr('/x', 'life', [42])
49+
call h%writeattr('/x', 'life_float', [42._real32, 84._real32])
50+
call h%writeattr('/x', 'life_double', [42._real64])
51+
52+
call h%close()
53+
54+
end subroutine test_write_attributes
55+
56+
57+
subroutine test_read_attributes(path)
58+
59+
type(hdf5_file) :: h
60+
character(*), intent(in) :: path
61+
character(1024) :: attr_str
62+
integer :: attr_int(1)
63+
real(real32) :: attr32(2)
64+
real(real64) :: attr64(1)
65+
66+
integer :: x
67+
68+
call h%open(path, action='r', mpi=.true.)
69+
70+
call h%read('/x', x)
71+
if (x/=1) error stop 'readattr: unexpected value'
72+
73+
call h%readattr('/x', 'note', attr_str)
74+
if (attr_str /= 'this is just a little number') error stop 'readattr value note'
75+
76+
call h%readattr('/x', 'life', attr_int)
77+
if (attr_int(1) /= 42) error stop 'readattr: int'
78+
79+
call h%readattr('/x', 'life_float', attr32)
80+
if (any(attr32 /= [42._real32, 84._real32])) error stop 'readattr: real32'
81+
82+
call h%readattr('/x', 'life_double', attr64)
83+
if (attr64(1) /= 42._real64) error stop 'readattr: real64'
84+
85+
call h%close()
86+
87+
end subroutine test_read_attributes
88+
89+
end program

0 commit comments

Comments
 (0)