ruby-hdf5 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/hdf5/dtype.rb ADDED
@@ -0,0 +1,200 @@
1
+ module HDF5
2
+ class DType
3
+ TYPES = {
4
+ int8: [Numo::Int8, :H5T_NATIVE_INT8_g, :H5T_STD_I8LE_g, :integer, 1],
5
+ uint8: [Numo::UInt8, :H5T_NATIVE_UINT8_g, :H5T_STD_U8LE_g, :integer, 1],
6
+ int16: [Numo::Int16, :H5T_NATIVE_INT16_g, :H5T_STD_I16LE_g, :integer, 2],
7
+ uint16: [Numo::UInt16, :H5T_NATIVE_UINT16_g, :H5T_STD_U16LE_g, :integer, 2],
8
+ int32: [Numo::Int32, :H5T_NATIVE_INT32_g, :H5T_STD_I32LE_g, :integer, 4],
9
+ uint32: [Numo::UInt32, :H5T_NATIVE_UINT32_g, :H5T_STD_U32LE_g, :integer, 4],
10
+ int64: [Numo::Int64, :H5T_NATIVE_INT64_g, :H5T_STD_I64LE_g, :integer, 8],
11
+ uint64: [Numo::UInt64, :H5T_NATIVE_UINT64_g, :H5T_STD_U64LE_g, :integer, 8],
12
+ float32: [Numo::SFloat, :H5T_NATIVE_FLOAT, :H5T_IEEE_F32LE_g, :float, 4],
13
+ float64: [Numo::DFloat, :H5T_NATIVE_DOUBLE, :H5T_IEEE_F64LE_g, :float, 8],
14
+ bool: [Numo::Bit, nil, nil, :bool, 1],
15
+ complex64: [Numo::SComplex, nil, nil, :complex, 8],
16
+ complex128: [Numo::DComplex, nil, nil, :complex, 16]
17
+ }.freeze
18
+
19
+ attr_reader :numo_class, :memory_type_name, :storage_type_name, :kind, :itemsize, :byteorder, :precision, :offset,
20
+ :hdf5_class
21
+
22
+ def self.for_numo(value)
23
+ type = TYPES.values.find { |numo_class,| value.is_a?(numo_class) }
24
+ raise HDF5::Error, "Unsupported Numo type: #{value.class}" unless type
25
+
26
+ symbol = TYPES.key(type)
27
+ new(symbol, *type)
28
+ end
29
+
30
+ def self.for_symbol(symbol)
31
+ type = TYPES.fetch(symbol) { raise HDF5::Error, "Unsupported dtype: #{symbol.inspect}" }
32
+ new(symbol, *type)
33
+ end
34
+
35
+ def self.for_hdf5(type_id)
36
+ type_class = HDF5::FFI.H5Tget_class(type_id)
37
+ itemsize = HDF5::FFI.H5Tget_size(type_id)
38
+ return for_bool_hdf5(type_id, itemsize) if type_class == :H5T_ENUM
39
+ return for_complex_hdf5(type_id, itemsize) if type_class == :H5T_COMPOUND
40
+
41
+ symbol = case type_class
42
+ when :H5T_INTEGER
43
+ prefix = HDF5::FFI.H5Tget_sign(type_id) == :H5T_SGN_NONE ? 'uint' : 'int'
44
+ "#{prefix}#{itemsize * 8}".to_sym
45
+ when :H5T_FLOAT
46
+ "float#{itemsize * 8}".to_sym
47
+ else
48
+ raise UnsupportedTypeError, "Unsupported HDF5 datatype: #{type_class}"
49
+ end
50
+
51
+ precision = HDF5::FFI.H5Tget_precision(type_id)
52
+ offset = HDF5::FFI.H5Tget_offset(type_id)
53
+ unless precision == itemsize * 8
54
+ raise UnsupportedTypeError,
55
+ "Unsupported #{precision}-bit datatype in #{itemsize * 8}-bit storage"
56
+ end
57
+ raise UnsupportedTypeError, "Unsupported datatype bit offset: #{offset}" unless offset.zero?
58
+
59
+ order = HDF5::FFI.H5Tget_order(type_id)
60
+ byteorder = { H5T_ORDER_LE: :little, H5T_ORDER_BE: :big, H5T_ORDER_NONE: :none }.fetch(order) do
61
+ raise UnsupportedTypeError, "Unsupported datatype byte order: #{order}"
62
+ end
63
+ new(symbol, *TYPES.fetch(symbol), byteorder:, precision:, offset:, hdf5_class: type_class)
64
+ end
65
+
66
+ def self.for_bool_hdf5(type_id, itemsize)
67
+ false_value = ::FFI::MemoryPointer.new(:int8)
68
+ true_value = ::FFI::MemoryPointer.new(:int8)
69
+ valid = itemsize == 1 && HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
70
+ HDF5::FFI.H5Tenum_valueof(type_id, 'FALSE', false_value) >= 0 &&
71
+ HDF5::FFI.H5Tenum_valueof(type_id, 'TRUE', true_value) >= 0 &&
72
+ false_value.read_int8.zero? && true_value.read_int8 == 1
73
+ raise UnsupportedTypeError, 'Unsupported HDF5 enum datatype' unless valid
74
+
75
+ new(:bool, *TYPES.fetch(:bool), byteorder: :none, hdf5_class: :H5T_ENUM)
76
+ end
77
+
78
+ def self.for_complex_hdf5(type_id, itemsize)
79
+ component_size = itemsize / 2
80
+ real_index = HDF5::FFI.H5Tget_member_index(type_id, 'r')
81
+ imaginary_index = HDF5::FFI.H5Tget_member_index(type_id, 'i')
82
+ valid = [8, 16].include?(itemsize) && real_index >= 0 && imaginary_index >= 0 &&
83
+ HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
84
+ HDF5::FFI.H5Tget_member_offset(type_id, real_index) == 0 &&
85
+ HDF5::FFI.H5Tget_member_offset(type_id, imaginary_index) == component_size
86
+ [real_index, imaginary_index].each do |index|
87
+ next unless index >= 0
88
+
89
+ member_type_id = HDF5::FFI.H5Tget_member_type(type_id, index)
90
+ valid &&= member_type_id >= 0 && HDF5::FFI.H5Tget_class(member_type_id) == :H5T_FLOAT &&
91
+ HDF5::FFI.H5Tget_size(member_type_id) == component_size
92
+ HDF5::FFI.H5Tclose(member_type_id) if member_type_id >= 0
93
+ end
94
+ raise UnsupportedTypeError, 'Unsupported HDF5 compound datatype' unless valid
95
+
96
+ symbol = itemsize == 8 ? :complex64 : :complex128
97
+ new(symbol, *TYPES.fetch(symbol), hdf5_class: :H5T_COMPOUND)
98
+ end
99
+
100
+ def initialize(symbol, numo_class, memory_type_name, storage_type_name, kind, itemsize, byteorder: :little,
101
+ precision: itemsize * 8, offset: 0, hdf5_class: nil)
102
+ @symbol = symbol
103
+ @numo_class = numo_class
104
+ @memory_type_name = memory_type_name
105
+ @storage_type_name = storage_type_name
106
+ @kind = kind
107
+ @itemsize = itemsize
108
+ @byteorder = byteorder
109
+ @precision = precision
110
+ @offset = offset
111
+ @hdf5_class = hdf5_class || (kind == :integer ? :H5T_INTEGER : :H5T_FLOAT)
112
+ freeze
113
+ end
114
+
115
+ def memory_type_id
116
+ return self.class.bool_type_id(native: true) if kind == :bool
117
+ return self.class.complex_type_id(itemsize, native: true) if kind == :complex
118
+
119
+ HDF5::FFI.public_send(memory_type_name)
120
+ end
121
+
122
+ def storage_type_id
123
+ return self.class.bool_type_id(native: false) if kind == :bool
124
+ return self.class.complex_type_id(itemsize, native: false) if kind == :complex
125
+
126
+ HDF5::FFI.public_send(storage_type_name)
127
+ end
128
+
129
+ def to_sym
130
+ @symbol
131
+ end
132
+
133
+ def castable_to?(target, casting: :safe)
134
+ raise ArgumentError, "Unsupported casting mode: #{casting.inspect}" unless %i[safe unsafe].include?(casting)
135
+ return true if casting == :unsafe || to_sym == target.to_sym
136
+
137
+ if kind == :integer && target.kind == :integer
138
+ return itemsize <= target.itemsize if unsigned? == target.unsigned?
139
+ return false unless unsigned? && !target.unsigned?
140
+
141
+ return itemsize < target.itemsize
142
+ end
143
+ return itemsize <= target.itemsize if kind == :float && target.kind == :float
144
+
145
+ if kind == :integer && target.kind == :float
146
+ significant_bits = unsigned? ? itemsize * 8 : itemsize * 8 - 1
147
+ mantissa_bits = target.itemsize == 4 ? 24 : 53
148
+ return significant_bits <= mantissa_bits
149
+ end
150
+
151
+ false
152
+ end
153
+
154
+ def unsigned?
155
+ @symbol.to_s.start_with?('uint')
156
+ end
157
+
158
+ class << self
159
+ def bool_type_id(native:)
160
+ @bool_type_ids ||= {}
161
+ @bool_type_ids[native] ||= begin
162
+ base_id = HDF5::FFI.public_send(native ? :H5T_NATIVE_INT8_g : :H5T_STD_I8LE_g)
163
+ type_id = HDF5::FFI.H5Tenum_create(base_id)
164
+ raise HDF5::Error, 'Failed to create bool datatype' if type_id < 0
165
+
166
+ false_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(0) }
167
+ true_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(1) }
168
+ if HDF5::FFI.H5Tenum_insert(type_id, 'FALSE', false_value) < 0 ||
169
+ HDF5::FFI.H5Tenum_insert(type_id, 'TRUE', true_value) < 0
170
+ HDF5::FFI.H5Tclose(type_id)
171
+ raise HDF5::Error, 'Failed to define bool datatype'
172
+ end
173
+
174
+ type_id
175
+ end
176
+ end
177
+
178
+ def complex_type_id(size, native:)
179
+ @complex_type_ids ||= {}
180
+ @complex_type_ids[[size, native]] ||= begin
181
+ component_id = if native
182
+ HDF5::FFI.public_send(size == 8 ? :H5T_NATIVE_FLOAT : :H5T_NATIVE_DOUBLE)
183
+ else
184
+ HDF5::FFI.public_send(size == 8 ? :H5T_IEEE_F32LE_g : :H5T_IEEE_F64LE_g)
185
+ end
186
+ type_id = HDF5::FFI.H5Tcreate(:H5T_COMPOUND, size)
187
+ raise HDF5::Error, 'Failed to create complex datatype' if type_id < 0
188
+
189
+ if HDF5::FFI.H5Tinsert(type_id, 'r', 0, component_id) < 0 ||
190
+ HDF5::FFI.H5Tinsert(type_id, 'i', size / 2, component_id) < 0
191
+ HDF5::FFI.H5Tclose(type_id)
192
+ raise HDF5::Error, 'Failed to define complex datatype'
193
+ end
194
+
195
+ type_id
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
data/lib/hdf5/ffi.rb CHANGED
@@ -2,6 +2,8 @@ module HDF5
2
2
  module FFI
3
3
  extend ::FFI::Library
4
4
 
5
+ require 'monitor'
6
+
5
7
  class << self
6
8
  attr_reader :backend
7
9
  end
@@ -28,6 +30,20 @@ module HDF5
28
30
  warn e.message # if $VERBOSE
29
31
  end
30
32
 
33
+ def self.backend_for_version(major, minor, release)
34
+ case [major, minor]
35
+ in [1, 10..13]
36
+ 'ffi_10'
37
+ in [1, 14..]
38
+ 'ffi_14'
39
+ in [2.., _]
40
+ 'ffi_20'
41
+ else
42
+ raise "Unsupported HDF5 version #{major}.#{minor}.#{release}"
43
+ end
44
+ end
45
+ private_class_method :backend_for_version
46
+
31
47
  attach_function 'H5get_libversion', %i[
32
48
  pointer
33
49
  pointer
@@ -43,17 +59,39 @@ module HDF5
43
59
  minor = minor_ptr.read_uint
44
60
  release = release_ptr.read_uint
45
61
 
46
- @backend = case [major, minor]
47
- in [1, 10..13]
48
- 'ffi_10'
49
- in [1, 14..]
50
- 'ffi_14'
51
- in [2.., _]
52
- 'ffi_14'
53
- else
54
- raise "Unsupported HDF5 version #{major}.#{minor}.#{release}"
55
- end
62
+ @backend = backend_for_version(major, minor, release)
56
63
 
57
64
  require_relative @backend
65
+
66
+ REQUIRED_FUNCTIONS = %i[
67
+ H5Fopen H5Fcreate H5Fclose H5Fflush
68
+ H5Gopen2 H5Gcreate2 H5Gclose
69
+ H5Dopen2 H5Dcreate2 H5Dclose H5Dread H5Dwrite H5Dget_type H5Dget_space H5Dset_extent H5Dget_create_plist
70
+ H5Aopen H5Acreate2 H5Aclose H5Aread H5Awrite H5Aexists H5Adelete H5Aiterate2 H5Aget_type H5Aget_space
71
+ H5Screate H5Screate_simple H5Sclose H5Sget_simple_extent_type H5Sget_simple_extent_ndims
72
+ H5Sget_simple_extent_dims H5Sselect_hyperslab H5Sget_select_npoints
73
+ H5Tcopy H5Tclose H5Tget_class H5Tget_size H5Tget_sign H5Tget_order H5Tget_precision H5Tget_offset
74
+ H5Tset_size H5Tset_cset H5Tis_variable_str H5Tcreate H5Tinsert H5Tenum_create H5Tenum_insert
75
+ H5Tenum_valueof H5Tget_nmembers H5Tget_member_index H5Tget_member_offset H5Tget_member_type H5Tget_super
76
+ H5Pcreate H5Pclose H5Pset_chunk H5Pget_chunk H5Pget_layout H5Pset_deflate H5Pset_shuffle H5Pset_fletcher32
77
+ H5Pset_fill_value H5Pget_fill_value H5Zfilter_avail H5Zget_filter_info
78
+ H5Lexists H5Ldelete H5Lmove H5Lcreate_hard H5Lcreate_soft H5Lget_val H5Dvlen_reclaim
79
+ ].freeze
80
+ version_functions = if MiV == 10
81
+ %i[H5Literate H5Lget_info H5Oget_info_by_name]
82
+ else
83
+ %i[H5Literate2 H5Lget_info2 H5Oget_info_by_name1]
84
+ end
85
+ missing = (REQUIRED_FUNCTIONS + version_functions).reject { |name| respond_to?(name) }
86
+ raise LoadError, "Loaded HDF5 library is missing required APIs: #{missing.join(', ')}" unless missing.empty?
87
+
88
+ CALL_LOCK = Monitor.new
89
+ synchronized_calls = Module.new
90
+ singleton_methods.grep(/^H5/).each do |name|
91
+ synchronized_calls.define_method(name) do |*args, **kwargs, &block|
92
+ CALL_LOCK.synchronize { super(*args, **kwargs, &block) }
93
+ end
94
+ end
95
+ singleton_class.prepend(synchronized_calls)
58
96
  end
59
97
  end