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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4591bafb2585ec00df81fc3831cc7037cc02dee81f15919ec7cf1b5d2f08a4c2
4
- data.tar.gz: beb661f3c825adaab6b132cee69a402c1319d05143a0bec1516f2e0dec1180b2
3
+ metadata.gz: 85ca4ff79d4a34553188284efb980628cd4f4931aa29d12bd37b09e0a89d3ca8
4
+ data.tar.gz: 1126e3458c88e00b00a6919ed832c35196673a5d785da524a218a9ff0772bd03
5
5
  SHA512:
6
- metadata.gz: bf13704291dfbdb2d0d61cf03504574a553e3d16e6db980355df5408ee425e340cfa1bc7b3850c4f877aa066958f20ab4cb5cbb07935f6302eb403771c6b4458
7
- data.tar.gz: ac8511ab873fea84933c38c65c15c899cfb3dbc7a63d2a062f41d78722bcc87288d84997e5f296c38691deb2660e065046e7fdf13e2c611127ef8fc84b1ce2ea
6
+ metadata.gz: 15f984a33a2c9036364e4bd4cef11acd041a1684f531cd30f05c235e764df5ff5a69cea2e7e727f0c34fd1438272673ed3764661bf1f0e19b0322f0879b4fea0
7
+ data.tar.gz: 9ef886f63b1c207e6c58e9b5a00462207d74239e522cb2aaeb9344237e93e14e6ad29326d3e79d7e9a1ea05b5a3d0215b0f5f5a250c412e9459a9810835f2aeb
data/README.md CHANGED
@@ -2,119 +2,122 @@
2
2
 
3
3
  [![test](https://github.com/red-data-tools/ruby-hdf5/actions/workflows/test.yml/badge.svg)](https://github.com/red-data-tools/ruby-hdf5/actions/workflows/test.yml)
4
4
 
5
- Ruby bindings for the HDF5 library.
5
+ Ruby bindings for HDF5 with Numo::NArray support.
6
6
 
7
- ## Scope
7
+ ## Requirements
8
8
 
9
- This gem currently provides practical high-level wrappers for:
10
-
11
- - opening and creating files
12
- - creating groups
13
- - creating, writing, and reading one-dimensional numeric datasets
14
- - reading and writing numeric attributes
15
-
16
- Unsupported at this stage:
17
-
18
- - string dataset read/write
19
- - multidimensional array write
20
-
21
- Integer datasets and integer attributes currently use native C `int` under the hood.
22
- Values outside that range are rejected with `HDF5::Error` to avoid silent overflow.
23
-
24
- `Group#list_datasets` filters datasets from group entries by checking object type per entry.
25
- For very large groups, this may be slower than `Group#list_entries`.
26
-
27
- ## Supported HDF5 Versions
28
-
29
- - HDF5 1.10
30
- - HDF5 1.14
31
- - HDF5 2.x
32
-
33
- HDF5 versions older than 1.10 are not supported.
9
+ - Ruby 3.4 or later
10
+ - HDF5 1.10 or later (`libhdf5` shared library)
34
11
 
35
12
  ## Install
36
13
 
37
- Add to your Gemfile:
14
+ Add the gem to your Gemfile:
38
15
 
39
16
  ```ruby
40
17
  gem 'ruby-hdf5'
41
18
  ```
42
19
 
43
- Install:
20
+ Then install dependencies:
44
21
 
45
22
  ```sh
46
23
  bundle install
47
24
  ```
48
25
 
49
- System library (`libhdf5`) is required.
50
-
51
- ## Runtime Notes
52
-
53
- - The gem loads `libhdf5` through FFI.
54
- - If the shared library cannot be found automatically, set `HDF5_LIB_PATH`.
55
-
56
- Examples:
26
+ Set `HDF5_LIB_PATH` only when `libhdf5` cannot be found automatically. It may be a library directory or a shared-library path.
57
27
 
58
28
  ```sh
59
- # Point to a directory containing libhdf5.so
60
- export HDF5_LIB_PATH=/usr/lib
61
-
62
- # Or point directly to the shared object
63
29
  export HDF5_LIB_PATH=/usr/lib/libhdf5.so
64
30
  ```
65
31
 
66
- ## Quick Start
32
+ ## Usage
67
33
 
68
- ### Read an existing file
34
+ Create a file and write a Numo array:
69
35
 
70
36
  ```ruby
71
37
  require 'hdf5'
72
38
 
73
- HDF5::File.open('example.h5') do |file|
74
- group = file['foo']
75
- dataset = group['bar_int']
39
+ matrix = Numo::SFloat.new(100, 64).seq
40
+
41
+ HDF5::File.open('numbers.h5', 'w') do |file|
42
+ dataset = file.require_group('measurements').create_dataset('signal', matrix)
43
+ dataset.attrs['unit'] = 'a.u.'
44
+ end
45
+ ```
46
+
47
+ Read data and inspect its type:
48
+
49
+ ```ruby
50
+ HDF5::File.open('numbers.h5') do |file|
51
+ dataset = file['measurements/signal']
76
52
  p dataset.shape
77
- p dataset.dtype
53
+ p dataset.dtype.to_sym
78
54
  p dataset.read
79
55
  end
80
56
  ```
81
57
 
82
- ### Create and write a file
58
+ Use a block with `HDF5::File.open` to close the file automatically. Supported modes are `r`, `r+`, `w`, `x`, and `a`.
83
59
 
84
- ```ruby
85
- require 'hdf5'
60
+ ## Common Tasks
86
61
 
87
- HDF5::File.create('numbers.h5') do |file|
88
- file.create_group('values') do |group|
89
- group.create_dataset('ints', [1, 2, 3, 4])
90
- end
91
- end
62
+ Read a row, a column, or a strided selection without reading the complete dataset:
92
63
 
93
- reopened = HDF5::File.open('numbers.h5')
94
- p reopened['values']['ints'].read
95
- reopened.close
64
+ ```ruby
65
+ HDF5::File.open('numbers.h5') do |file|
66
+ dataset = file['measurements/signal']
67
+ row = dataset[10, true]
68
+ column = dataset[true, 0]
69
+ every_tenth_row = dataset[HDF5.slice(0...100, step: 10), true]
70
+ end
96
71
  ```
97
72
 
98
- ## Error Handling
73
+ Append rows to an extendible dataset. Extendible datasets must use chunked storage:
74
+
75
+ ```ruby
76
+ HDF5::File.open('samples.h5', 'w') do |file|
77
+ samples = file.create_dataset(
78
+ 'samples',
79
+ shape: [0, 2],
80
+ dtype: :float32,
81
+ maxshape: [nil, 2],
82
+ chunks: [256, 2]
83
+ )
84
+ samples.append(Numo::SFloat[[1.0, 2.0], [3.0, 4.0]])
85
+ end
86
+ ```
99
87
 
100
- High-level API failures raise `HDF5::Error`.
88
+ Process a dataset in bounded-memory blocks:
101
89
 
102
90
  ```ruby
103
- begin
104
- HDF5::File.open('missing.h5')
105
- rescue HDF5::Error => e
106
- warn e.message
91
+ sum = 0.0
92
+
93
+ HDF5::File.open('numbers.h5') do |file|
94
+ file['measurements/signal'].each_block(max_bytes: 4 * 1024 * 1024) do |_selection, block|
95
+ sum += block.sum
96
+ end
107
97
  end
108
98
  ```
109
99
 
110
- ## Development
100
+ ## Main Operations
101
+
102
+ - Hierarchy: `[]`, `create_group`, `require_group`, `keys`, `delete`, `move`
103
+ - Datasets: `create_dataset`, `read`, `write`, `[]`, `[]=`, `read_into`
104
+ - Dataset metadata: `shape`, `ndim`, `size`, `dtype`, `chunks`, `maxshape`, `fillvalue`
105
+ - Storage: chunking, gzip, shuffle, Fletcher32, resize, and append
106
+ - Iteration: `each_block(max_bytes:)` and `each_chunk`
107
+ - Attributes: `attrs[]`, `attrs[]=`, `attrs.create`, `attrs.modify`, `attrs.delete`
108
+
109
+ Datasets support Numo numeric arrays, scalar values, variable-length UTF-8 strings, h5py-compatible bool values, and h5py-compatible complex values.
110
+
111
+ ## Limitations
111
112
 
112
- After more than a decade, it is clear that the Ruby community does not have enough resources to sustainably maintain an HDF5 library. For that reason, development of this library is intentionally AI-assisted. Something is better than nothing.
113
+ - Fixed-length strings, general compound / enum / reference types, and variable-length numeric types are unsupported.
114
+ - Fancy indexing, boolean masks, negative slice steps, and general broadcasting are unsupported.
115
+ - SWMR, MPI, and VDS creation are unsupported.
113
116
 
114
- ## Acknowledgement
117
+ ## Examples
115
118
 
116
- [https://github.com/edmundhighcock/hdf5](https://github.com/edmundhighcock/hdf5)
119
+ See [examples/README.md](examples/README.md) for standalone examples, ordered from basic file I/O through chunking, resizing, and links.
117
120
 
118
121
  ## License
119
122
 
120
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
123
+ MIT. See [LICENSE.txt](LICENSE.txt).
@@ -1,43 +1,36 @@
1
1
  module HDF5
2
2
  class Attribute
3
- def initialize(dataset_id, attr_name)
3
+ def initialize(dataset_id, attr_name, context = nil)
4
4
  @dataset_id = dataset_id
5
5
  @attr_name = attr_name
6
+ context&.ensure_open!(dataset_id)
6
7
  @attr_id = HDF5::FFI.H5Aopen(@dataset_id, @attr_name, HDF5::DEFAULT_PROPERTY_LIST)
7
8
  raise HDF5::Error, 'Failed to open attribute' if @attr_id < 0
8
9
  end
9
10
 
10
11
  def read
11
12
  type_id = HDF5::FFI.H5Aget_type(@attr_id)
13
+ raise HDF5::Error, 'Failed to get attribute datatype' if type_id < 0
14
+
12
15
  space_id = HDF5::FFI.H5Aget_space(@attr_id)
16
+ raise HDF5::Error, 'Failed to get attribute dataspace' if space_id < 0
17
+ return HDF5::Empty.new(DType.for_hdf5(type_id)) if HDF5::FFI.H5Sget_simple_extent_type(space_id) == :H5S_NULL
13
18
 
14
- size = HDF5::FFI.H5Sget_simple_extent_npoints(space_id)
15
-
16
- buffer = \
17
- case HDF5::FFI.H5Tget_class(type_id)
18
- when :H5T_INTEGER
19
- ::FFI::MemoryPointer.new(:int, size)
20
- when :H5T_FLOAT
21
- ::FFI::MemoryPointer.new(:double, size)
22
- when :H5T_STRING
23
- ::FFI::MemoryPointer.new(:pointer, size)
24
- else
25
- raise HDF5::Error, 'Unsupported data type'
26
- end
19
+ return read_string(type_id, space_id) if HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
27
20
 
28
- status = HDF5::FFI.H5Aread(@attr_id, type_id, buffer)
21
+ dtype_object = DType.for_hdf5(type_id)
22
+ attribute_shape = shape(space_id)
23
+ size = attribute_shape.empty? ? 1 : attribute_shape.inject(:*)
24
+ buffer = ::FFI::MemoryPointer.new(:char, size * dtype_object.itemsize)
25
+ status = HDF5::FFI.H5Aread(@attr_id, dtype_object.memory_type_id, buffer)
29
26
  raise HDF5::Error, 'Failed to read attribute' if status < 0
30
27
 
31
- case HDF5::FFI.H5Tget_class(type_id)
32
- when :H5T_INTEGER
33
- buffer.read_array_of_int(size)
34
- when :H5T_FLOAT
35
- buffer.read_array_of_double(size)
36
- when :H5T_STRING
37
- buffer.read_pointer.read_string
38
- else
39
- raise HDF5::Error, 'Unsupported data type'
40
- end
28
+ result = HDF5::DataHelpers.from_binary(dtype_object, buffer.read_bytes(size * dtype_object.itemsize),
29
+ attribute_shape)
30
+ return result unless attribute_shape.empty?
31
+
32
+ scalar = result.extract
33
+ dtype_object.kind == :bool ? !scalar.zero? : scalar
41
34
  ensure
42
35
  HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
43
36
  HDF5::FFI.H5Sclose(space_id) if space_id && space_id >= 0
@@ -49,15 +42,54 @@ module HDF5
49
42
  HDF5::FFI.H5Aclose(@attr_id)
50
43
  @attr_id = nil
51
44
  end
45
+
46
+ private
47
+
48
+ def shape(space_id)
49
+ rank = HDF5::FFI.H5Sget_simple_extent_ndims(space_id)
50
+ raise HDF5::Error, 'Failed to get attribute rank' if rank < 0
51
+ return [] if rank.zero?
52
+
53
+ dimensions = ::FFI::MemoryPointer.new(:ulong_long, rank)
54
+ status = HDF5::FFI.H5Sget_simple_extent_dims(space_id, dimensions, nil)
55
+ raise HDF5::Error, 'Failed to get attribute shape' if status < 0
56
+
57
+ dimensions.read_array_of_uint64(rank)
58
+ end
59
+
60
+ def read_string(type_id, space_id)
61
+ unless HDF5::StringCodec.variable?(type_id)
62
+ raise UnsupportedTypeError, 'Fixed-length string attributes are not yet supported'
63
+ end
64
+
65
+ attribute_shape = shape(space_id)
66
+ count = attribute_shape.empty? ? 1 : attribute_shape.inject(:*)
67
+ buffer = ::FFI::MemoryPointer.new(:pointer, count)
68
+ status = HDF5::FFI.H5Aread(@attr_id, type_id, buffer)
69
+ raise HDF5::Error, 'Failed to read string attribute' if status < 0
70
+
71
+ HDF5::StringCodec.read_values(buffer, count, attribute_shape, encoding: HDF5::StringCodec.encoding_for(type_id))
72
+ ensure
73
+ if buffer
74
+ active_error = $ERROR_INFO
75
+ reclaim_status = HDF5::FFI.H5Dvlen_reclaim(type_id, space_id, HDF5::DEFAULT_PROPERTY_LIST, buffer)
76
+ if reclaim_status.negative? && active_error.nil?
77
+ raise HDF5::Error,
78
+ 'Failed to reclaim variable-length string attribute'
79
+ end
80
+ end
81
+ end
52
82
  end
53
83
 
54
84
  class AttributeManager
55
- def initialize(dataset_id)
85
+ def initialize(dataset_id, context = nil)
56
86
  @dataset_id = dataset_id
87
+ @context = context
57
88
  end
58
89
 
59
90
  def [](attr_name)
60
- attr = Attribute.new(@dataset_id, attr_name)
91
+ @context&.ensure_open!(@dataset_id)
92
+ attr = Attribute.new(@dataset_id, attr_name, @context)
61
93
  attr.read
62
94
  ensure
63
95
  attr.close if attr
@@ -67,9 +99,102 @@ module HDF5
67
99
  write(attr_name, value)
68
100
  end
69
101
 
102
+ def keys
103
+ @context&.ensure_open!(@dataset_id)
104
+ names = []
105
+ index = ::FFI::MemoryPointer.new(:ulong_long)
106
+ index.write_ulong_long(0)
107
+ callback = ::FFI::Function.new(:int, %i[int64_t string pointer pointer]) do |_, name, _, _|
108
+ names << name
109
+ 0
110
+ end
111
+ status = HDF5::FFI.H5Aiterate2(@dataset_id, :H5_INDEX_NAME, :H5_ITER_NATIVE, index, callback, nil)
112
+ raise HDF5::Error, 'Failed to iterate over attributes' if status < 0
113
+
114
+ names
115
+ end
116
+
117
+ def key?(attr_name)
118
+ @context&.ensure_open!(@dataset_id)
119
+ exists = HDF5::FFI.H5Aexists(@dataset_id, attr_name)
120
+ raise HDF5::Error, "Failed to check attribute existence: #{attr_name}" if exists.negative?
121
+
122
+ exists.positive?
123
+ end
124
+
125
+ def delete(attr_name)
126
+ @context&.ensure_open!(@dataset_id)
127
+ status = HDF5::FFI.H5Adelete(@dataset_id, attr_name)
128
+ raise HDF5::Error, "Failed to delete attribute: #{attr_name}" if status < 0
129
+
130
+ self
131
+ end
132
+
133
+ def create(attr_name, value)
134
+ raise HDF5::Error, "Attribute already exists: #{attr_name}" if key?(attr_name)
135
+
136
+ write(attr_name, value)
137
+ end
138
+
139
+ def modify(attr_name, value, casting: :safe)
140
+ @context&.ensure_open!(@dataset_id)
141
+ raise HDF5::Error, "Attribute not found: #{attr_name}" unless key?(attr_name)
142
+
143
+ attr_id = HDF5::FFI.H5Aopen(@dataset_id, attr_name, HDF5::DEFAULT_PROPERTY_LIST)
144
+ raise HDF5::Error, "Failed to open attribute: #{attr_name}" if attr_id < 0
145
+
146
+ type_id = HDF5::FFI.H5Aget_type(attr_id)
147
+ space_id = HDF5::FFI.H5Aget_space(attr_id)
148
+ raise HDF5::Error, "Failed to inspect attribute: #{attr_name}" if type_id < 0 || space_id < 0
149
+ if HDF5::FFI.H5Sget_simple_extent_type(space_id) == :H5S_NULL
150
+ unless value.is_a?(HDF5::Empty) && value.dtype.to_sym == DType.for_hdf5(type_id).to_sym
151
+ raise HDF5::ShapeError, 'Cannot assign a value to a Null attribute'
152
+ end
153
+ return value
154
+ end
155
+
156
+ if HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
157
+ unless HDF5::StringCodec.variable?(type_id)
158
+ raise UnsupportedTypeError, 'Fixed-length string attributes are not yet supported'
159
+ end
160
+
161
+ encoding = HDF5::StringCodec.encoding_for(type_id)
162
+ string_values, string_shape = HDF5::StringCodec.normalize_data(value, encoding:)
163
+ unless string_shape == attribute_shape(space_id)
164
+ raise HDF5::ShapeError,
165
+ 'Attribute shape must not change when modifying'
166
+ end
167
+
168
+ buffer, _string_pointers = HDF5::StringCodec.buffer_for_values(string_values, encoding:)
169
+ status = HDF5::FFI.H5Awrite(attr_id, type_id, buffer)
170
+ else
171
+ dtype_object = DType.for_hdf5(type_id)
172
+ values = HDF5::DataHelpers.normalize_data(value, label: 'Attribute data', dtype: dtype_object, casting:, convert: false)
173
+ expected_shape = attribute_shape(space_id)
174
+ raise HDF5::Error, 'Attribute shape must not change when modifying' unless values.shape == expected_shape
175
+
176
+ status = HDF5::FFI.H5Awrite(attr_id, DType.for_numo(values).memory_type_id, HDF5::DataHelpers.buffer_for(values))
177
+ end
178
+ raise HDF5::Error, "Failed to modify attribute: #{attr_name}" if status < 0
179
+
180
+ value
181
+ ensure
182
+ HDF5::FFI.H5Sclose(space_id) if space_id && space_id >= 0
183
+ HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
184
+ HDF5::FFI.H5Aclose(attr_id) if attr_id && attr_id >= 0
185
+ end
186
+
70
187
  def write(attr_name, value)
71
- values = normalize_data(value)
72
- datatype_id = datatype_id_for(values)
188
+ @context&.ensure_open!(@dataset_id)
189
+ empty_data = value.is_a?(HDF5::Empty)
190
+ if empty_data && value.dtype.kind == :string
191
+ raise UnsupportedFeatureError, 'Creating Null string attributes is not yet supported'
192
+ end
193
+ string_data = HDF5::StringCodec.string_data?(value)
194
+ string_values, string_shape = HDF5::StringCodec.normalize_data(value) if string_data
195
+ values = HDF5::DataHelpers.normalize_data(value, label: 'Attribute data') unless string_data || empty_data
196
+ dtype_object = empty_data ? value.dtype : DType.for_numo(values) unless string_data
197
+ type_id = string_data ? HDF5::StringCodec.datatype_id : dtype_object.storage_type_id
73
198
 
74
199
  exists = HDF5::FFI.H5Aexists(@dataset_id, attr_name)
75
200
  raise HDF5::Error, "Failed to check attribute existence: #{attr_name}" if exists.negative?
@@ -79,78 +204,60 @@ module HDF5
79
204
  raise HDF5::Error, "Failed to replace attribute: #{attr_name}" if status < 0
80
205
  end
81
206
 
82
- dims = ::FFI::MemoryPointer.new(:ulong_long, 1)
83
- dims.write_array_of_ulong_long([values.length])
84
- dataspace_id = HDF5::FFI.H5Screate_simple(1, dims, nil)
207
+ dataspace_id = create_dataspace(empty_data ? nil : (string_data ? string_shape : values.shape))
85
208
  raise HDF5::Error, 'Failed to create attribute dataspace' if dataspace_id < 0
86
209
 
87
210
  attr_id = HDF5::FFI.H5Acreate2(
88
211
  @dataset_id,
89
212
  attr_name,
90
- datatype_id,
213
+ type_id,
91
214
  dataspace_id,
92
215
  HDF5::DEFAULT_PROPERTY_LIST,
93
216
  HDF5::DEFAULT_PROPERTY_LIST
94
217
  )
95
218
  raise HDF5::Error, "Failed to create attribute: #{attr_name}" if attr_id < 0
219
+ return value if empty_data
96
220
 
97
- buffer = buffer_for(values)
98
- status = HDF5::FFI.H5Awrite(attr_id, datatype_id, buffer)
221
+ buffer, _string_pointers = if string_data
222
+ HDF5::StringCodec.buffer_for_values(string_values)
223
+ else
224
+ [HDF5::DataHelpers.buffer_for(values), nil]
225
+ end
226
+ memory_type_id = string_data ? type_id : dtype_object.memory_type_id
227
+ status = HDF5::FFI.H5Awrite(attr_id, memory_type_id, buffer)
99
228
  raise HDF5::Error, "Failed to write attribute: #{attr_name}" if status < 0
100
229
 
101
230
  value
102
231
  ensure
103
232
  HDF5::FFI.H5Aclose(attr_id) if attr_id && attr_id >= 0
104
233
  HDF5::FFI.H5Sclose(dataspace_id) if dataspace_id && dataspace_id >= 0
234
+ HDF5::FFI.H5Tclose(type_id) if string_data && type_id && type_id >= 0
105
235
  end
106
236
 
107
237
  private
108
238
 
109
- def normalize_data(value)
110
- values = value.is_a?(Array) ? value : [value]
111
- raise HDF5::Error, 'Attribute data must not be empty' if values.empty?
112
- raise HDF5::Error, 'Nested arrays are not supported for attributes' if values.any? { |item| item.is_a?(Array) }
239
+ def attribute_shape(space_id)
240
+ rank = HDF5::FFI.H5Sget_simple_extent_ndims(space_id)
241
+ raise HDF5::Error, 'Failed to get attribute rank' if rank < 0
242
+ return [] if rank.zero?
113
243
 
114
- values
115
- end
244
+ dimensions = ::FFI::MemoryPointer.new(:ulong_long, rank)
245
+ status = HDF5::FFI.H5Sget_simple_extent_dims(space_id, dimensions, nil)
246
+ raise HDF5::Error, 'Failed to get attribute shape' if status < 0
116
247
 
117
- def datatype_id_for(values)
118
- if values.all? { |item| item.is_a?(Integer) }
119
- validate_native_int_range!(values)
120
- HDF5::FFI.H5T_NATIVE_INT
121
- elsif values.all? { |item| item.is_a?(Numeric) }
122
- HDF5::FFI.H5T_NATIVE_DOUBLE
123
- else
124
- raise HDF5::Error, 'Only numeric attribute data is supported'
125
- end
248
+ dimensions.read_array_of_uint64(rank)
126
249
  end
127
250
 
128
- def buffer_for(values)
129
- if values.all? { |item| item.is_a?(Integer) }
130
- buffer = ::FFI::MemoryPointer.new(:int, values.length)
131
- buffer.write_array_of_int(values)
132
- else
133
- buffer = ::FFI::MemoryPointer.new(:double, values.length)
134
- buffer.write_array_of_double(values.map(&:to_f))
135
- end
251
+ def create_dataspace(shape)
252
+ return HDF5::FFI.H5Screate(:H5S_NULL) if shape.nil?
136
253
 
137
- buffer
138
- end
254
+ return HDF5::FFI.H5Screate(:H5S_SCALAR) if shape.empty?
139
255
 
140
- def native_int_bounds
141
- bits = ::FFI.type_size(:int) * 8
142
- max = (1 << (bits - 1)) - 1
143
- min = -(1 << (bits - 1))
144
- [min, max]
256
+ dimensions = ::FFI::MemoryPointer.new(:ulong_long, shape.length)
257
+ dimensions.write_array_of_ulong_long(shape)
258
+ HDF5::FFI.H5Screate_simple(shape.length, dimensions, nil)
145
259
  end
146
260
 
147
- def validate_native_int_range!(values)
148
- min, max = native_int_bounds
149
- out_of_range = values.find { |value| value < min || value > max }
150
- return unless out_of_range
151
-
152
- raise HDF5::Error,
153
- "Integer value #{out_of_range} is outside native int range (#{min}..#{max}). Use a smaller value."
154
- end
261
+ prepend FileContext.guard(:[], :[]=, :keys, :key?, :delete, :create, :modify, :write)
155
262
  end
156
263
  end