Class: HDF5::DType

Inherits:
Object
  • Object
show all
Defined in:
lib/hdf5/dtype.rb

Constant Summary collapse

TYPES =
{
  int8: [Numo::Int8, :H5T_NATIVE_INT8_g, :H5T_STD_I8LE_g, :integer, 1],
  uint8: [Numo::UInt8, :H5T_NATIVE_UINT8_g, :H5T_STD_U8LE_g, :integer, 1],
  int16: [Numo::Int16, :H5T_NATIVE_INT16_g, :H5T_STD_I16LE_g, :integer, 2],
  uint16: [Numo::UInt16, :H5T_NATIVE_UINT16_g, :H5T_STD_U16LE_g, :integer, 2],
  int32: [Numo::Int32, :H5T_NATIVE_INT32_g, :H5T_STD_I32LE_g, :integer, 4],
  uint32: [Numo::UInt32, :H5T_NATIVE_UINT32_g, :H5T_STD_U32LE_g, :integer, 4],
  int64: [Numo::Int64, :H5T_NATIVE_INT64_g, :H5T_STD_I64LE_g, :integer, 8],
  uint64: [Numo::UInt64, :H5T_NATIVE_UINT64_g, :H5T_STD_U64LE_g, :integer, 8],
  float32: [Numo::SFloat, :H5T_NATIVE_FLOAT, :H5T_IEEE_F32LE_g, :float, 4],
  float64: [Numo::DFloat, :H5T_NATIVE_DOUBLE, :H5T_IEEE_F64LE_g, :float, 8],
  bool: [Numo::Bit, nil, nil, :bool, 1],
  complex64: [Numo::SComplex, nil, nil, :complex, 8],
  complex128: [Numo::DComplex, nil, nil, :complex, 16]
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(symbol, numo_class, memory_type_name, storage_type_name, kind, itemsize, byteorder: :little, precision: itemsize * 8, offset: 0, hdf5_class: nil, encoding: nil) ⇒ DType

Returns a new instance of DType.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/hdf5/dtype.rb', line 125

def initialize(symbol, numo_class, memory_type_name, storage_type_name, kind, itemsize, byteorder: :little,
               precision: itemsize * 8, offset: 0, hdf5_class: nil, encoding: nil)
  @symbol = symbol
  @numo_class = numo_class
  @memory_type_name = memory_type_name
  @storage_type_name = storage_type_name
  @kind = kind
  @itemsize = itemsize
  @byteorder = byteorder
  @precision = precision
  @offset = offset
  @encoding = encoding
  @hdf5_class = hdf5_class || (kind == :integer ? :H5T_INTEGER : :H5T_FLOAT)
  freeze
end

Instance Attribute Details

#byteorderObject (readonly)

Returns the value of attribute byteorder.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def byteorder
  @byteorder
end

#encodingObject (readonly)

Returns the value of attribute encoding.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def encoding
  @encoding
end

#hdf5_classObject (readonly)

Returns the value of attribute hdf5_class.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def hdf5_class
  @hdf5_class
end

#itemsizeObject (readonly)

Returns the value of attribute itemsize.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def itemsize
  @itemsize
end

#kindObject (readonly)

Returns the value of attribute kind.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def kind
  @kind
end

#memory_type_nameObject (readonly)

Returns the value of attribute memory_type_name.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def memory_type_name
  @memory_type_name
end

#numo_classObject (readonly)

Returns the value of attribute numo_class.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def numo_class
  @numo_class
end

#offsetObject (readonly)

Returns the value of attribute offset.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def offset
  @offset
end

#precisionObject (readonly)

Returns the value of attribute precision.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def precision
  @precision
end

#storage_type_nameObject (readonly)

Returns the value of attribute storage_type_name.



19
20
21
# File 'lib/hdf5/dtype.rb', line 19

def storage_type_name
  @storage_type_name
end

Class Method Details

.bool_type_id(native:) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/hdf5/dtype.rb', line 192

def bool_type_id(native:)
  @bool_type_ids ||= {}
  @bool_type_ids[native] ||= begin
    base_id = HDF5::FFI.public_send(native ? :H5T_NATIVE_INT8_g : :H5T_STD_I8LE_g)
    type_id = HDF5::FFI.H5Tenum_create(base_id)
    raise NativeError, 'Failed to create bool datatype' if type_id < 0

    false_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(0) }
    true_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(1) }
    if HDF5::FFI.H5Tenum_insert(type_id, 'FALSE', false_value) < 0 ||
       HDF5::FFI.H5Tenum_insert(type_id, 'TRUE', true_value) < 0
      HDF5::FFI.H5Tclose(type_id)
      raise NativeError, 'Failed to define bool datatype'
    end

    type_id
  end
end

.complex_type_id(size, native:) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/hdf5/dtype.rb', line 211

def complex_type_id(size, native:)
  @complex_type_ids ||= {}
  @complex_type_ids[[size, native]] ||= begin
    component_id = if native
                     HDF5::FFI.public_send(size == 8 ? :H5T_NATIVE_FLOAT : :H5T_NATIVE_DOUBLE)
                   else
                     HDF5::FFI.public_send(size == 8 ? :H5T_IEEE_F32LE_g : :H5T_IEEE_F64LE_g)
                   end
    type_id = HDF5::FFI.H5Tcreate(:H5T_COMPOUND, size)
    raise NativeError, 'Failed to create complex datatype' if type_id < 0

    if HDF5::FFI.H5Tinsert(type_id, 'r', 0, component_id) < 0 ||
       HDF5::FFI.H5Tinsert(type_id, 'i', size / 2, component_id) < 0
      HDF5::FFI.H5Tclose(type_id)
      raise NativeError, 'Failed to define complex datatype'
    end

    type_id
  end
end

.for_bool_hdf5(type_id, itemsize) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/hdf5/dtype.rb', line 85

def self.for_bool_hdf5(type_id, itemsize)
  false_value = ::FFI::MemoryPointer.new(:int8)
  true_value = ::FFI::MemoryPointer.new(:int8)
  valid = itemsize == 1 && HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
          HDF5::FFI.H5Tenum_valueof(type_id, 'FALSE', false_value) >= 0 &&
          HDF5::FFI.H5Tenum_valueof(type_id, 'TRUE', true_value) >= 0 &&
          false_value.read_int8.zero? && true_value.read_int8 == 1
  raise UnsupportedTypeError, 'Unsupported HDF5 enum datatype' unless valid

  new(:bool, *TYPES.fetch(:bool), byteorder: :none, hdf5_class: :H5T_ENUM)
end

.for_complex_hdf5(type_id, itemsize) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/hdf5/dtype.rb', line 97

def self.for_complex_hdf5(type_id, itemsize)
  component_size = itemsize / 2
  real_index = HDF5::FFI.H5Tget_member_index(type_id, 'r')
  imaginary_index = HDF5::FFI.H5Tget_member_index(type_id, 'i')
  valid = [8, 16].include?(itemsize) && real_index >= 0 && imaginary_index >= 0 &&
          HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
          HDF5::FFI.H5Tget_member_offset(type_id, real_index) == 0 &&
          HDF5::FFI.H5Tget_member_offset(type_id, imaginary_index) == component_size
  byteorders = []
  [real_index, imaginary_index].each do |index|
    next unless index >= 0

    member_type_id = HDF5::FFI.H5Tget_member_type(type_id, index)
    begin
      member_dtype = member_type_id >= 0 ? for_hdf5(member_type_id) : nil
      valid &&= member_dtype && member_dtype.kind == :float && member_dtype.itemsize == component_size
      byteorders << member_dtype.byteorder if member_dtype
    ensure
      Native.close([:H5Tclose, member_type_id])
    end
  end
  valid &&= byteorders.length == 2 && byteorders.uniq.length == 1
  raise UnsupportedTypeError, 'Unsupported HDF5 compound datatype' unless valid

  symbol = itemsize == 8 ? :complex64 : :complex128
  new(symbol, *TYPES.fetch(symbol), byteorder: byteorders.first, hdf5_class: :H5T_COMPOUND)
end

.for_hdf5(type_id) ⇒ Object

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/hdf5/dtype.rb', line 40

def self.for_hdf5(type_id)
  type_class = Native.datatype_class(type_id)
  itemsize = HDF5::FFI.H5Tget_size(type_id)
  raise NativeError, 'Failed to get datatype size' if itemsize.zero?
  if type_class == :H5T_STRING
    return new(:string, Numo::RObject, nil, nil, :string, itemsize, byteorder: :none,
               hdf5_class: type_class, encoding: StringCodec.encoding_for(type_id))
  end
  return for_bool_hdf5(type_id, itemsize) if type_class == :H5T_ENUM
  return for_complex_hdf5(type_id, itemsize) if type_class == :H5T_COMPOUND

  symbol = case type_class
           when :H5T_INTEGER
             sign = HDF5::FFI.H5Tget_sign(type_id)
             raise NativeError, 'Failed to get datatype sign' if sign == :H5T_SGN_ERROR

             prefix = sign == :H5T_SGN_NONE ? 'uint' : 'int'
             "#{prefix}#{itemsize * 8}".to_sym
           when :H5T_FLOAT
             "float#{itemsize * 8}".to_sym
           else
             raise UnsupportedTypeError, "Unsupported HDF5 datatype: #{type_class}"
           end

  precision = HDF5::FFI.H5Tget_precision(type_id)
  raise NativeError, 'Failed to get datatype precision' if precision.zero?

  offset = HDF5::FFI.H5Tget_offset(type_id)
  Native.check(offset, 'Failed to get datatype bit offset')
  unless precision == itemsize * 8
    raise UnsupportedTypeError,
          "Unsupported #{precision}-bit datatype in #{itemsize * 8}-bit storage"
  end
  raise UnsupportedTypeError, "Unsupported datatype bit offset: #{offset}" unless offset.zero?

  order = HDF5::FFI.H5Tget_order(type_id)
  raise NativeError, 'Failed to get datatype byte order' if order == :H5T_ORDER_ERROR

  byteorder = { H5T_ORDER_LE: :little, H5T_ORDER_BE: :big, H5T_ORDER_NONE: :none }.fetch(order) do
    raise UnsupportedTypeError, "Unsupported datatype byte order: #{order}"
  end
  type = TYPES.fetch(symbol) { raise UnsupportedTypeError, "Unsupported HDF5 datatype size: #{itemsize}" }
  new(symbol, *type, byteorder:, precision:, offset:, hdf5_class: type_class)
end

.for_numo(value) ⇒ Object



22
23
24
25
26
27
28
# File 'lib/hdf5/dtype.rb', line 22

def self.for_numo(value)
  type = TYPES.values.find { |numo_class,| value.is_a?(numo_class) }
  raise UnsupportedTypeError, "Unsupported Numo type: #{value.class}" unless type

  symbol = TYPES.key(type)
  new(symbol, *type)
end

.for_symbol(symbol) ⇒ Object



30
31
32
33
34
35
36
37
38
# File 'lib/hdf5/dtype.rb', line 30

def self.for_symbol(symbol)
  if symbol == :string
    return new(:string, Numo::RObject, nil, nil, :string, ::FFI.type_size(:pointer), byteorder: :none,
               hdf5_class: :H5T_STRING, encoding: Encoding::UTF_8)
  end

  type = TYPES.fetch(symbol) { raise UnsupportedTypeError, "Unsupported dtype: #{symbol.inspect}" }
  new(symbol, *type)
end

Instance Method Details

#castable_to?(target, casting: :safe) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/hdf5/dtype.rb', line 163

def castable_to?(target, casting: :safe)
  DataHelpers.validate_casting!(casting)
  return true if casting == :unsafe || to_sym == target.to_sym

  if kind == :integer && target.kind == :integer
    return itemsize <= target.itemsize if unsigned? == target.unsigned?
    return false unless unsigned? && !target.unsigned?

    return itemsize < target.itemsize
  end
  return itemsize <= target.itemsize if kind == :float && target.kind == :float
  return itemsize <= target.itemsize if kind == :complex && target.kind == :complex
  return itemsize <= target.itemsize / 2 if kind == :float && target.kind == :complex

  if kind == :integer && %i[float complex].include?(target.kind)
    significant_bits = unsigned? ? itemsize * 8 : itemsize * 8 - 1
    size = target.kind == :complex ? target.itemsize / 2 : target.itemsize
    mantissa_bits = size == 4 ? 24 : 53
    return significant_bits <= mantissa_bits
  end

  false
end

#memory_type_idObject



141
142
143
144
145
146
147
148
# File 'lib/hdf5/dtype.rb', line 141

def memory_type_id
  raise UnsupportedTypeError, 'String datatype IDs must be owned by the string codec caller' if kind == :string

  return self.class.bool_type_id(native: true) if kind == :bool
  return self.class.complex_type_id(itemsize, native: true) if kind == :complex

  HDF5::FFI.public_send(memory_type_name)
end

#storage_type_idObject



150
151
152
153
154
155
156
157
# File 'lib/hdf5/dtype.rb', line 150

def storage_type_id
  raise UnsupportedTypeError, 'String datatype IDs must be owned by the string codec caller' if kind == :string

  return self.class.bool_type_id(native: false) if kind == :bool
  return self.class.complex_type_id(itemsize, native: false) if kind == :complex

  HDF5::FFI.public_send(storage_type_name)
end

#to_symObject



159
160
161
# File 'lib/hdf5/dtype.rb', line 159

def to_sym
  @symbol
end

#unsigned?Boolean

Returns:

  • (Boolean)


187
188
189
# File 'lib/hdf5/dtype.rb', line 187

def unsigned?
  @symbol.to_s.start_with?('uint')
end