ruby-hdf5 0.0.2 → 0.0.4

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,214 @@
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, :encoding
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
+ if type_class == :H5T_STRING
39
+ return new(:string, Numo::RObject, nil, nil, :string, itemsize, byteorder: :none,
40
+ hdf5_class: type_class, encoding: StringCodec.encoding_for(type_id))
41
+ end
42
+ return for_bool_hdf5(type_id, itemsize) if type_class == :H5T_ENUM
43
+ return for_complex_hdf5(type_id, itemsize) if type_class == :H5T_COMPOUND
44
+
45
+ symbol = case type_class
46
+ when :H5T_INTEGER
47
+ prefix = HDF5::FFI.H5Tget_sign(type_id) == :H5T_SGN_NONE ? 'uint' : 'int'
48
+ "#{prefix}#{itemsize * 8}".to_sym
49
+ when :H5T_FLOAT
50
+ "float#{itemsize * 8}".to_sym
51
+ else
52
+ raise UnsupportedTypeError, "Unsupported HDF5 datatype: #{type_class}"
53
+ end
54
+
55
+ precision = HDF5::FFI.H5Tget_precision(type_id)
56
+ offset = HDF5::FFI.H5Tget_offset(type_id)
57
+ unless precision == itemsize * 8
58
+ raise UnsupportedTypeError,
59
+ "Unsupported #{precision}-bit datatype in #{itemsize * 8}-bit storage"
60
+ end
61
+ raise UnsupportedTypeError, "Unsupported datatype bit offset: #{offset}" unless offset.zero?
62
+
63
+ order = HDF5::FFI.H5Tget_order(type_id)
64
+ byteorder = { H5T_ORDER_LE: :little, H5T_ORDER_BE: :big, H5T_ORDER_NONE: :none }.fetch(order) do
65
+ raise UnsupportedTypeError, "Unsupported datatype byte order: #{order}"
66
+ end
67
+ new(symbol, *TYPES.fetch(symbol), byteorder:, precision:, offset:, hdf5_class: type_class)
68
+ end
69
+
70
+ def self.for_bool_hdf5(type_id, itemsize)
71
+ false_value = ::FFI::MemoryPointer.new(:int8)
72
+ true_value = ::FFI::MemoryPointer.new(:int8)
73
+ valid = itemsize == 1 && HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
74
+ HDF5::FFI.H5Tenum_valueof(type_id, 'FALSE', false_value) >= 0 &&
75
+ HDF5::FFI.H5Tenum_valueof(type_id, 'TRUE', true_value) >= 0 &&
76
+ false_value.read_int8.zero? && true_value.read_int8 == 1
77
+ raise UnsupportedTypeError, 'Unsupported HDF5 enum datatype' unless valid
78
+
79
+ new(:bool, *TYPES.fetch(:bool), byteorder: :none, hdf5_class: :H5T_ENUM)
80
+ end
81
+
82
+ def self.for_complex_hdf5(type_id, itemsize)
83
+ component_size = itemsize / 2
84
+ real_index = HDF5::FFI.H5Tget_member_index(type_id, 'r')
85
+ imaginary_index = HDF5::FFI.H5Tget_member_index(type_id, 'i')
86
+ valid = [8, 16].include?(itemsize) && real_index >= 0 && imaginary_index >= 0 &&
87
+ HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
88
+ HDF5::FFI.H5Tget_member_offset(type_id, real_index) == 0 &&
89
+ HDF5::FFI.H5Tget_member_offset(type_id, imaginary_index) == component_size
90
+ byteorders = []
91
+ [real_index, imaginary_index].each do |index|
92
+ next unless index >= 0
93
+
94
+ member_type_id = HDF5::FFI.H5Tget_member_type(type_id, index)
95
+ begin
96
+ member_dtype = member_type_id >= 0 ? for_hdf5(member_type_id) : nil
97
+ valid &&= member_dtype && member_dtype.kind == :float && member_dtype.itemsize == component_size
98
+ byteorders << member_dtype.byteorder if member_dtype
99
+ ensure
100
+ HDF5::FFI.H5Tclose(member_type_id) if member_type_id >= 0
101
+ end
102
+ end
103
+ valid &&= byteorders.length == 2 && byteorders.uniq.length == 1
104
+ raise UnsupportedTypeError, 'Unsupported HDF5 compound datatype' unless valid
105
+
106
+ symbol = itemsize == 8 ? :complex64 : :complex128
107
+ new(symbol, *TYPES.fetch(symbol), byteorder: byteorders.first, hdf5_class: :H5T_COMPOUND)
108
+ end
109
+
110
+ def initialize(symbol, numo_class, memory_type_name, storage_type_name, kind, itemsize, byteorder: :little,
111
+ precision: itemsize * 8, offset: 0, hdf5_class: nil, encoding: nil)
112
+ @symbol = symbol
113
+ @numo_class = numo_class
114
+ @memory_type_name = memory_type_name
115
+ @storage_type_name = storage_type_name
116
+ @kind = kind
117
+ @itemsize = itemsize
118
+ @byteorder = byteorder
119
+ @precision = precision
120
+ @offset = offset
121
+ @encoding = encoding
122
+ @hdf5_class = hdf5_class || (kind == :integer ? :H5T_INTEGER : :H5T_FLOAT)
123
+ freeze
124
+ end
125
+
126
+ def memory_type_id
127
+ return self.class.bool_type_id(native: true) if kind == :bool
128
+ return self.class.complex_type_id(itemsize, native: true) if kind == :complex
129
+
130
+ HDF5::FFI.public_send(memory_type_name)
131
+ end
132
+
133
+ def storage_type_id
134
+ return self.class.bool_type_id(native: false) if kind == :bool
135
+ return self.class.complex_type_id(itemsize, native: false) if kind == :complex
136
+
137
+ HDF5::FFI.public_send(storage_type_name)
138
+ end
139
+
140
+ def to_sym
141
+ @symbol
142
+ end
143
+
144
+ def castable_to?(target, casting: :safe)
145
+ raise ArgumentError, "Unsupported casting mode: #{casting.inspect}" unless %i[safe unsafe].include?(casting)
146
+ return true if casting == :unsafe || to_sym == target.to_sym
147
+
148
+ if kind == :integer && target.kind == :integer
149
+ return itemsize <= target.itemsize if unsigned? == target.unsigned?
150
+ return false unless unsigned? && !target.unsigned?
151
+
152
+ return itemsize < target.itemsize
153
+ end
154
+ return itemsize <= target.itemsize if kind == :float && target.kind == :float
155
+ return itemsize <= target.itemsize if kind == :complex && target.kind == :complex
156
+ return itemsize <= target.itemsize / 2 if kind == :float && target.kind == :complex
157
+
158
+ if kind == :integer && %i[float complex].include?(target.kind)
159
+ significant_bits = unsigned? ? itemsize * 8 : itemsize * 8 - 1
160
+ size = target.kind == :complex ? target.itemsize / 2 : target.itemsize
161
+ mantissa_bits = size == 4 ? 24 : 53
162
+ return significant_bits <= mantissa_bits
163
+ end
164
+
165
+ false
166
+ end
167
+
168
+ def unsigned?
169
+ @symbol.to_s.start_with?('uint')
170
+ end
171
+
172
+ class << self
173
+ def bool_type_id(native:)
174
+ @bool_type_ids ||= {}
175
+ @bool_type_ids[native] ||= begin
176
+ base_id = HDF5::FFI.public_send(native ? :H5T_NATIVE_INT8_g : :H5T_STD_I8LE_g)
177
+ type_id = HDF5::FFI.H5Tenum_create(base_id)
178
+ raise HDF5::Error, 'Failed to create bool datatype' if type_id < 0
179
+
180
+ false_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(0) }
181
+ true_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(1) }
182
+ if HDF5::FFI.H5Tenum_insert(type_id, 'FALSE', false_value) < 0 ||
183
+ HDF5::FFI.H5Tenum_insert(type_id, 'TRUE', true_value) < 0
184
+ HDF5::FFI.H5Tclose(type_id)
185
+ raise HDF5::Error, 'Failed to define bool datatype'
186
+ end
187
+
188
+ type_id
189
+ end
190
+ end
191
+
192
+ def complex_type_id(size, native:)
193
+ @complex_type_ids ||= {}
194
+ @complex_type_ids[[size, native]] ||= begin
195
+ component_id = if native
196
+ HDF5::FFI.public_send(size == 8 ? :H5T_NATIVE_FLOAT : :H5T_NATIVE_DOUBLE)
197
+ else
198
+ HDF5::FFI.public_send(size == 8 ? :H5T_IEEE_F32LE_g : :H5T_IEEE_F64LE_g)
199
+ end
200
+ type_id = HDF5::FFI.H5Tcreate(:H5T_COMPOUND, size)
201
+ raise HDF5::Error, 'Failed to create complex datatype' if type_id < 0
202
+
203
+ if HDF5::FFI.H5Tinsert(type_id, 'r', 0, component_id) < 0 ||
204
+ HDF5::FFI.H5Tinsert(type_id, 'i', size / 2, component_id) < 0
205
+ HDF5::FFI.H5Tclose(type_id)
206
+ raise HDF5::Error, 'Failed to define complex datatype'
207
+ end
208
+
209
+ type_id
210
+ end
211
+ end
212
+ end
213
+ end
214
+ 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, 0..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 H5Tget_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