nmatrix 0.0.1 → 0.0.2

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.
Files changed (91) hide show
  1. data/.gitignore +27 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -5
  4. data/Guardfile +6 -0
  5. data/History.txt +33 -0
  6. data/Manifest.txt +41 -38
  7. data/README.rdoc +88 -11
  8. data/Rakefile +35 -53
  9. data/ext/nmatrix/data/complex.h +372 -0
  10. data/ext/nmatrix/data/data.cpp +275 -0
  11. data/ext/nmatrix/data/data.h +707 -0
  12. data/ext/nmatrix/data/rational.h +421 -0
  13. data/ext/nmatrix/data/ruby_object.h +446 -0
  14. data/ext/nmatrix/extconf.rb +101 -51
  15. data/ext/nmatrix/new_extconf.rb +56 -0
  16. data/ext/nmatrix/nmatrix.cpp +1609 -0
  17. data/ext/nmatrix/nmatrix.h +265 -849
  18. data/ext/nmatrix/ruby_constants.cpp +134 -0
  19. data/ext/nmatrix/ruby_constants.h +103 -0
  20. data/ext/nmatrix/storage/common.cpp +70 -0
  21. data/ext/nmatrix/storage/common.h +170 -0
  22. data/ext/nmatrix/storage/dense.cpp +665 -0
  23. data/ext/nmatrix/storage/dense.h +116 -0
  24. data/ext/nmatrix/storage/list.cpp +1088 -0
  25. data/ext/nmatrix/storage/list.h +129 -0
  26. data/ext/nmatrix/storage/storage.cpp +658 -0
  27. data/ext/nmatrix/storage/storage.h +99 -0
  28. data/ext/nmatrix/storage/yale.cpp +1601 -0
  29. data/ext/nmatrix/storage/yale.h +208 -0
  30. data/ext/nmatrix/ttable_helper.rb +126 -0
  31. data/ext/nmatrix/{yale/smmp1_header.template.c → types.h} +36 -9
  32. data/ext/nmatrix/util/io.cpp +295 -0
  33. data/ext/nmatrix/util/io.h +117 -0
  34. data/ext/nmatrix/util/lapack.h +1175 -0
  35. data/ext/nmatrix/util/math.cpp +557 -0
  36. data/ext/nmatrix/util/math.h +1363 -0
  37. data/ext/nmatrix/util/sl_list.cpp +475 -0
  38. data/ext/nmatrix/util/sl_list.h +255 -0
  39. data/ext/nmatrix/util/util.h +78 -0
  40. data/lib/nmatrix/blas.rb +70 -0
  41. data/lib/nmatrix/io/mat5_reader.rb +567 -0
  42. data/lib/nmatrix/io/mat_reader.rb +162 -0
  43. data/lib/{string.rb → nmatrix/monkeys.rb} +49 -2
  44. data/lib/nmatrix/nmatrix.rb +199 -0
  45. data/lib/nmatrix/nvector.rb +103 -0
  46. data/lib/nmatrix/version.rb +27 -0
  47. data/lib/nmatrix.rb +22 -230
  48. data/nmatrix.gemspec +59 -0
  49. data/scripts/mac-brew-gcc.sh +47 -0
  50. data/spec/4x4_sparse.mat +0 -0
  51. data/spec/4x5_dense.mat +0 -0
  52. data/spec/blas_spec.rb +47 -0
  53. data/spec/elementwise_spec.rb +164 -0
  54. data/spec/io_spec.rb +60 -0
  55. data/spec/lapack_spec.rb +52 -0
  56. data/spec/math_spec.rb +96 -0
  57. data/spec/nmatrix_spec.rb +93 -89
  58. data/spec/nmatrix_yale_spec.rb +52 -36
  59. data/spec/nvector_spec.rb +1 -1
  60. data/spec/slice_spec.rb +257 -0
  61. data/spec/spec_helper.rb +51 -0
  62. data/spec/utm5940.mtx +83844 -0
  63. metadata +113 -71
  64. data/.autotest +0 -23
  65. data/.gemtest +0 -0
  66. data/ext/nmatrix/cblas.c +0 -150
  67. data/ext/nmatrix/dense/blas_header.template.c +0 -52
  68. data/ext/nmatrix/dense/elementwise.template.c +0 -107
  69. data/ext/nmatrix/dense/gemm.template.c +0 -159
  70. data/ext/nmatrix/dense/gemv.template.c +0 -130
  71. data/ext/nmatrix/dense/rationalmath.template.c +0 -68
  72. data/ext/nmatrix/dense.c +0 -307
  73. data/ext/nmatrix/depend +0 -18
  74. data/ext/nmatrix/generator/syntax_tree.rb +0 -481
  75. data/ext/nmatrix/generator.rb +0 -594
  76. data/ext/nmatrix/list.c +0 -774
  77. data/ext/nmatrix/nmatrix.c +0 -1977
  78. data/ext/nmatrix/rational.c +0 -98
  79. data/ext/nmatrix/yale/complexmath.template.c +0 -71
  80. data/ext/nmatrix/yale/elementwise.template.c +0 -46
  81. data/ext/nmatrix/yale/elementwise_op.template.c +0 -73
  82. data/ext/nmatrix/yale/numbmm.template.c +0 -94
  83. data/ext/nmatrix/yale/smmp1.template.c +0 -21
  84. data/ext/nmatrix/yale/smmp2.template.c +0 -43
  85. data/ext/nmatrix/yale/smmp2_header.template.c +0 -46
  86. data/ext/nmatrix/yale/sort_columns.template.c +0 -56
  87. data/ext/nmatrix/yale/symbmm.template.c +0 -54
  88. data/ext/nmatrix/yale/transp.template.c +0 -68
  89. data/ext/nmatrix/yale.c +0 -726
  90. data/lib/array.rb +0 -67
  91. data/spec/syntax_tree_spec.rb +0 -46
@@ -0,0 +1,567 @@
1
+ # = NMatrix
2
+ #
3
+ # A linear algebra library for scientific computation in Ruby.
4
+ # NMatrix is part of SciRuby.
5
+ #
6
+ # NMatrix was originally inspired by and derived from NArray, by
7
+ # Masahiro Tanaka: http://narray.rubyforge.org
8
+ #
9
+ # == Copyright Information
10
+ #
11
+ # SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
12
+ # NMatrix is Copyright (c) 2012, Ruby Science Foundation
13
+ #
14
+ # Please see LICENSE.txt for additional copyright notices.
15
+ #
16
+ # == Contributing
17
+ #
18
+ # By contributing source code to SciRuby, you agree to be bound by
19
+ # our Contributor Agreement:
20
+ #
21
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
22
+ #
23
+ # == io/matlab/mat5_reader.rb
24
+ #
25
+ # Matlab version 5 .mat file reader (and eventually writer too).
26
+ #
27
+
28
+
29
+ require_relative 'mat_reader.rb'
30
+
31
+ module NMatrix::IO::Matlab
32
+ # Reader (and eventual writer) for a version 5 .mat file.
33
+ class Mat5Reader < MatReader
34
+ attr_reader :file_header, :first_tag_field, :first_data_field
35
+
36
+ class Compressed
37
+ include Packable
38
+ # include TaggedDataEnumerable
39
+
40
+ attr_reader :byte_order
41
+
42
+ def initialize(stream = nil, byte_order = nil, content_or_bytes = nil)
43
+ @stream = stream
44
+ @byte_order = byte_order
45
+
46
+ if content_or_bytes.is_a?(String)
47
+ @content = content_or_bytes
48
+
49
+ elsif content_or_bytes.is_a?(Fixnum)
50
+ @padded_bytes = content_or_bytes
51
+ #else
52
+ # raise ArgumentError, "Need a content string or a number of bytes; content_or_bytes is #{content_or_bytes.class.to_s}."
53
+ end
54
+ end
55
+
56
+ def compressed
57
+ require "zlib"
58
+ # [2..-5] removes headers
59
+ @compressed ||= Zlib::Deflate.deflate(content)
60
+ end
61
+
62
+ def content
63
+ @content ||= extract
64
+ end
65
+
66
+ def padded_bytes
67
+ @padded_bytes ||= content.size % 4 == 0 ? content.size : (content.size / 4 + 1) * 4
68
+ end
69
+
70
+ def write_packed(packedio, options)
71
+ packedio << [compressed, {:bytes => padded_bytes}.merge(options)]
72
+ end
73
+
74
+ def read_packed(packedio, options)
75
+ @compressed = (packedio >> [String, options]).first
76
+ content
77
+ end
78
+
79
+ protected
80
+ def extract
81
+ require 'zlib'
82
+
83
+ zstream = Zlib::Inflate.new #(-Zlib::MAX_WBITS) # No header
84
+
85
+ returning(zstream.inflate(@compressed)) do
86
+ zstream.finish
87
+ zstream.close
88
+ end
89
+ end
90
+ end
91
+
92
+ MatrixDataStruct = Struct.new(
93
+ :cells, :logical, :global, :complex, :nonzero_max,
94
+ :matlab_class, :dimensions, :matlab_name, :real_part,
95
+ :imaginary_part, :row_index, :column_index)
96
+
97
+ class MatrixData < MatrixDataStruct
98
+ include Packable
99
+
100
+ def write_packed(packedio, options)
101
+ raise NotImplementedError
102
+ packedio << [info, {:bytes => padded_bytes}.merge(options)]
103
+ end
104
+
105
+ # Figure out the appropriate Ruby type to convert to, and do it. There are basically two possible types: NMatrix
106
+ # and Ruby Array. This function is recursive, so an Array is going to contain other Arrays and/or NMatrix objects.
107
+ #
108
+ # mxCELL types (cells) will be converted to the Array type.
109
+ #
110
+ # mxSPARSE and other types will be converted to NMatrix, with the appropriate stype (:yale or :dense, respectively).
111
+ #
112
+ # See also to_nm, which is responsible for NMatrix instantiation.
113
+ def to_ruby
114
+ case matlab_class
115
+ when :mxSPARSE then return to_nm
116
+ when :mxCELL then return self.cells.collect { |c| c.to_ruby }
117
+ else return to_nm
118
+ end
119
+ end
120
+
121
+ # Try to determine what dtype and such to use.
122
+ #
123
+ # TODO: Needs to be verified that unsigned MATLAB types are being converted to the correct NMatrix signed dtypes.
124
+ def guess_dtype_from_mdtype
125
+ dtype = MatReader::MDTYPE_TO_DTYPE[self.real_part.tag.data_type]
126
+
127
+ return dtype unless self.complex
128
+
129
+ dtype == :float32 ? :complex64 : :complex128
130
+ end
131
+
132
+ # Unpacks data without repacking it.
133
+ #
134
+ # Used only for dense matrix creation. Yale matrix creation uses repacked_data.
135
+ def unpacked_data real_mdtype=nil, imag_mdtype=nil
136
+ # Get Matlab data type and unpack args
137
+ real_mdtype ||= self.real_part.tag.data_type
138
+ real_unpack_args = MatReader::MDTYPE_UNPACK_ARGS[real_mdtype]
139
+
140
+ # zip real and complex components together, or just return real component
141
+ if self.complex
142
+ imag_mdtype ||= self.imaginary_part.tag.data_type
143
+ imag_unpack_args = MatReader::MDTYPE_UNPACK_ARGS[imag_mdtype]
144
+
145
+ unpacked_real = self.real_part.data.unpack(real_unpack_args)
146
+ unpacked_imag = self.imaginary_part.data.unpack(imag_unpack_args)
147
+
148
+ unpacked_real.zip(unpacked_imag).flatten
149
+ else
150
+ length = self.dimensions.inject(1) { |a,b| a * b } # get the product
151
+ self.real_part.data.unpack(*(real_unpack_args*length))
152
+ end
153
+
154
+ end
155
+
156
+ # Unpacks and repacks data into the appropriate format for NMatrix.
157
+ #
158
+ # If data is already in the appropriate format, does not unpack or repack, just returns directly.
159
+ #
160
+ # Complex is always unpacked and repacked, as the real and imaginary components must be merged together (MATLAB
161
+ # stores them separately for some crazy reason).
162
+ #
163
+ # Used only for Yale storage creation. For dense, see unpacked_data.
164
+ #
165
+ # This function calls repack and complex_merge, which are both defined in io.cpp.
166
+ def repacked_data(to_dtype = nil)
167
+
168
+ real_mdtype = self.real_part.tag.data_type
169
+
170
+ # Figure out what dtype to use based on the MATLAB data-types (mdtypes). They could be different for real and
171
+ # imaginary, so call upcast to figure out what to use.
172
+
173
+ components = [] # real and imaginary parts or just the real part
174
+
175
+ if self.complex
176
+ imag_mdtype = self.imaginary_part.tag.data_type
177
+
178
+ # Make sure we convert both mdtypes do the same dtype
179
+ to_dtype ||= NMatrix.upcast(MatReader::MDTYPE_TO_DTYPE[real_mdtype], MatReader::MDTYPE_TO_DTYPE[imag_mdtype])
180
+
181
+ # Let's make sure we don't try to send NMatrix complex integers. We need complex floating points.
182
+ unless [:float32, :float64].include?(to_dtype)
183
+ to_dtype = NMatrix.upcast(to_dtype, :float32)
184
+ end
185
+
186
+ STDERR.puts "imag: Requesting dtype #{to_dtype.inspect}"
187
+ # Repack the imaginary part
188
+ components[1] = ::NMatrix::IO::Matlab.repack( self.imaginary_part.data, imag_mdtype, :dtype => to_dtype )
189
+
190
+ else
191
+
192
+ to_dtype ||= MatReader::MDTYPE_TO_DTYPE[real_mdtype]
193
+
194
+ # Sometimes repacking isn't necessary -- sometimes the format is already good
195
+ if MatReader::NO_REPACK.include?(real_mdtype)
196
+ STDERR.puts "No repack"
197
+ return [self.real_part.data, to_dtype]
198
+ end
199
+
200
+ end
201
+
202
+ # Repack the real part
203
+ STDERR.puts "real: Requesting dtype #{to_dtype.inspect}"
204
+ components[0] = ::NMatrix::IO::Matlab.repack( self.real_part.data, real_mdtype, :dtype => to_dtype )
205
+
206
+ # Merge the two parts if complex, or just return the real part.
207
+ [self.complex ? ::NMatrix::IO::Matlab.complex_merge( components[0], components[1], to_dtype ) : components[0],
208
+ to_dtype]
209
+ end
210
+
211
+
212
+ # Unpacks and repacks index data into the appropriate format for NMatrix.
213
+ #
214
+ # If data is already in the appropriate format, does not unpack or repack, just returns directly.
215
+ def repacked_indices(to_itype)
216
+ return [row_index.data, column_index.data] if to_itype == :uint32 # No need to re-pack -- already correct
217
+
218
+ STDERR.puts "indices: Requesting itype #{to_itype.inspect}"
219
+ repacked_row_indices = ::NMatrix::IO::Matlab.repack( self.row_index.data, :miINT32, :itype => to_itype )
220
+ repacked_col_indices = ::NMatrix::IO::Matlab.repack( self.column_index.data, :miINT32, :itype => to_itype )
221
+
222
+ [repacked_row_indices, repacked_col_indices]
223
+ end
224
+
225
+
226
+ # Create an NMatrix from a MATLAB .mat (v5) matrix.
227
+ #
228
+ # This function matches the storage type exactly. That is, a regular matrix in MATLAB will be a dense NMatrix, and
229
+ # a sparse (old Yale) matrix in MATLAB will be a :yale (new Yale) matrix in NMatrix.
230
+ #
231
+ # Note that NMatrix has no old Yale type, so this uses a semi-hidden version of the NMatrix constructor to pass in
232
+ # -- as directly as possible -- the stored bytes in a MATLAB sparse matrix. This constructor should also be used
233
+ # for other IO formats that want to create sparse matrices from IA and JA vectors (e.g., SciPy).
234
+ #
235
+ # This is probably not the fastest code. An ideal solution would be a C plugin of some sort for reading the MATLAB
236
+ # .mat file. However, .mat v5 is a really complicated format, and lends itself to an object-oriented solution.
237
+ def to_nm(dtype = nil)
238
+ # Hardest part is figuring out from_dtype, from_index_dtype, and dtype.
239
+ dtype ||= guess_dtype_from_mdtype
240
+ from_dtype = MatReader::MDTYPE_TO_DTYPE[self.real_part.tag.data_type]
241
+
242
+ # Create the same kind of matrix that MATLAB saved.
243
+ case matlab_class
244
+ when :mxSPARSE
245
+ raise(NotImplementedError, "expected .mat row indices to be of type :miINT32") unless row_index.tag.data_type == :miINT32
246
+ raise(NotImplementedError, "expected .mat column indices to be of type :miINT32") unless column_index.tag.data_type == :miINT32
247
+
248
+ to_itype = NMatrix.itype_by_shape(dimensions)
249
+
250
+ #require 'pry'
251
+ #binding.pry
252
+
253
+ # MATLAB always uses :miINT32 for indices according to the spec
254
+ ia_ja = repacked_indices(to_itype)
255
+ data_str, repacked_dtype = repacked_data(dtype)
256
+ NMatrix.new(:yale, self.dimensions.reverse, repacked_dtype, ia_ja[0], ia_ja[1], data_str, repacked_dtype)
257
+
258
+ else
259
+ # Call regular dense constructor.
260
+ NMatrix.new(:dense, self.dimensions.reverse, unpacked_data, dtype).transpose
261
+ end
262
+ end
263
+
264
+ def read_packed(packedio, options)
265
+ flags_class, self.nonzero_max = packedio.read([Element, options]).data
266
+
267
+ self.matlab_class = MatReader::MCLASSES[flags_class % 16]
268
+ #STDERR.puts "Matrix class: #{self.matlab_class}"
269
+
270
+ self.logical = (flags_class >> 8) % 2 == 1 ? true : false
271
+ self.global = (flags_class >> 9) % 2 == 1 ? true : false
272
+ self.complex = (flags_class >> 10) % 2 == 1 ? true : false
273
+ #STDERR.puts "nzmax: #{self.nonzero_max}"
274
+
275
+ dimensions_tag_data = packedio.read([Element, options])
276
+ self.dimensions = dimensions_tag_data.data
277
+ #STDERR.puts "dimensions: #{self.dimensions}"
278
+
279
+ begin
280
+ name_tag_data = packedio.read([Element, options])
281
+ self.matlab_name = name_tag_data.data.is_a?(Array) ? name_tag_data.data.collect { |i| i.chr }.join('') : name_tag_data.data.chr
282
+
283
+ rescue ElementDataIOError => e
284
+ STDERR.puts "ERROR: Failure while trying to read Matlab variable name: #{name_tag_data.inspect}"
285
+ STDERR.puts 'Element Tag:'
286
+ STDERR.puts " #{e.tag}"
287
+ STDERR.puts 'Previously, I read these dimensions:'
288
+ STDERR.puts " #{dimensions_tag_data.inspect}"
289
+ STDERR.puts "Unpack options were: #{options.inspect}"
290
+ raise(e)
291
+ end
292
+
293
+ #STDERR.puts [flags_class.to_s(2), self.complex, self.global, self.logical, nil, self.mclass, self.nonzero_max].join("\t")
294
+ if self.matlab_class == :mxCELL
295
+ # Read what may be a series of matrices
296
+ self.cells = []
297
+ STDERR.puts("Warning: Cell array does not yet support reading multiple dimensions") if dimensions.size > 2 || (dimensions[0] > 1 && dimensions[1] > 1)
298
+ number_of_cells = dimensions.inject(1) { |prod,i| prod * i }
299
+ number_of_cells.times { self.cells << packedio.read([Element, options]) }
300
+
301
+ else
302
+ read_opts = [RawElement, {:bytes => options[:bytes], :endian => :native}]
303
+
304
+ if self.matlab_class == :mxSPARSE
305
+ self.column_index = packedio.read(read_opts)
306
+ self.row_index = packedio.read(read_opts)
307
+
308
+ # STDERR.puts "row and col indices: #{self.row_index.inspect}, #{self.column_index.inspect}"
309
+ end
310
+
311
+ self.real_part = packedio.read(read_opts)
312
+ self.imaginary_part = packedio.read(read_opts) if self.complex
313
+ end
314
+ end
315
+
316
+ def ignore_padding packedio, bytes
317
+ packedio.read([Integer, {:unsigned => true, :bytes => bytes}]) if bytes > 0
318
+ end
319
+ end
320
+
321
+
322
+ MDTYPE_UNPACK_ARGS =
323
+ MatReader::MDTYPE_UNPACK_ARGS.merge({
324
+ :miCOMPRESSED => [Compressed, {}],
325
+ :miMATRIX => [MatrixData, {}]
326
+ })
327
+ # include TaggedDataEnumerable
328
+
329
+ FIRST_TAG_FIELD_POS = 128
330
+
331
+ ####################
332
+ # Instance Methods #
333
+ ####################
334
+
335
+ def initialize(stream, options = {})
336
+ super(stream, options)
337
+ @file_header = seek_and_read_file_header
338
+ end
339
+
340
+ def to_a
341
+ returning(Array.new) do |ary|
342
+ self.each { |el| ary << el }
343
+ end
344
+ end
345
+
346
+ def to_ruby
347
+ ary = self.to_a
348
+
349
+ if ary.size == 1
350
+ ary.first.to_ruby
351
+ else
352
+ ary.collect { |item| item.to_ruby }
353
+ end
354
+ end
355
+
356
+ def guess_byte_order
357
+ stream.seek(Header::BYTE_ORDER_POS)
358
+ mi = stream.read(Header::BYTE_ORDER_LENGTH)
359
+ stream.seek(0)
360
+ mi == 'IM' ? :little : :big
361
+ end
362
+
363
+ def seek_and_read_file_header
364
+ stream.seek(0)
365
+ stream.read(FIRST_TAG_FIELD_POS).unpack(Header, {:endian => byte_order})
366
+ end
367
+
368
+ def each(&block)
369
+ stream.each(Element, {:endian => byte_order}) do |element|
370
+ if element.data.is_a?(Compressed)
371
+ StringIO.new(element.data.content, 'rb').each(Element, {:endian => byte_order}) do |compressed_element|
372
+ yield compressed_element.data
373
+ end
374
+
375
+ else
376
+ yield element.data
377
+ end
378
+ end
379
+
380
+ # Go back to the beginning in case we want to do it again.
381
+ stream.seek(FIRST_TAG_FIELD_POS)
382
+
383
+ self
384
+ end
385
+
386
+ ####################
387
+ # Internal Classes #
388
+ ####################
389
+
390
+ class Header < Struct.new(:desc, :data_offset, :version, :endian)
391
+
392
+ include Packable
393
+
394
+ BYTE_ORDER_LENGTH = 2
395
+ DESC_LENGTH = 116
396
+ DATA_OFFSET_LENGTH = 8
397
+ VERSION_LENGTH = 2
398
+ BYTE_ORDER_POS = 126
399
+
400
+ ## TODO: TEST WRITE.
401
+ def write_packed(packedio, options)
402
+ packedio << [desc, {:bytes => DESC_LENGTH }] <<
403
+ [data_offset, {:bytes => DATA_OFFSET_LENGTH }] <<
404
+ [version, {:bytes => VERSION_LENGTH }] <<
405
+ [byte_order, {:bytes => BYTE_ORDER_LENGTH }]
406
+ end
407
+
408
+ def read_packed(packedio, options)
409
+ self.desc, self.data_offset, self.version, self.endian = packedio >>
410
+ [String, {:bytes => DESC_LENGTH }] >>
411
+ [String, {:bytes => DATA_OFFSET_LENGTH }] >>
412
+ [Integer, {:bytes => VERSION_LENGTH, :endian => options[:endian] }] >>
413
+ [String, {:bytes => 2 }]
414
+
415
+ self.desc.strip!
416
+ self.data_offset.strip!
417
+ self.data_offset = nil if self.data_offset.empty?
418
+
419
+ self.endian == 'IM' ? :little : :big
420
+ end
421
+ end
422
+
423
+ class Tag < Struct.new(:data_type, :raw_data_type, :bytes, :small)
424
+ include Packable
425
+
426
+ DATA_TYPE_OPTS = BYTES_OPTS = {:bytes => 4, :signed => false}
427
+ LENGTH = DATA_TYPE_OPTS[:bytes] + BYTES_OPTS[:bytes]
428
+
429
+ ## TODO: TEST WRITE.
430
+ def write_packed packedio, options
431
+ packedio << [data_type, DATA_TYPE_OPTS] << [bytes, BYTES_OPTS]
432
+ end
433
+
434
+ def small?
435
+ self.bytes > 0 and self.bytes <= 4
436
+ end
437
+
438
+ def size
439
+ small? ? 4 : 8
440
+ end
441
+
442
+ def read_packed packedio, options
443
+ self.raw_data_type = packedio.read([Integer, DATA_TYPE_OPTS.merge(options)])
444
+
445
+ # Borrowed from a SciPy patch
446
+ upper = self.raw_data_type >> 16
447
+ lower = self.raw_data_type & 0xFFFF
448
+
449
+ if upper > 0
450
+ # Small data element format
451
+ raise IOError, 'Small data element format indicated, but length is more than 4 bytes!' if upper > 4
452
+
453
+ self.bytes = upper
454
+ self.raw_data_type = lower
455
+
456
+ else
457
+ self.bytes = packedio.read([Integer, BYTES_OPTS.merge(options)])
458
+ end
459
+
460
+ self.data_type = MatReader::MDTYPES[self.raw_data_type]
461
+ end
462
+
463
+ def inspect
464
+ "#<#{self.class.to_s} data_type=#{data_type}[#{raw_data_type}][#{raw_data_type.to_s(2)}] bytes=#{bytes} size=#{size}#{small? ? ' small' : ''}>"
465
+ end
466
+ end
467
+
468
+
469
+ class ElementDataIOError < IOError
470
+ attr_reader :tag
471
+
472
+ def initialize(tag = nil, msg = nil)
473
+ @tag = tag
474
+ super msg
475
+ end
476
+
477
+ def to_s
478
+ @tag.inspect + "\n" + super
479
+ end
480
+ end
481
+
482
+
483
+ class Element < Struct.new(:tag, :data)
484
+ include Packable
485
+
486
+ def write_packed packedio, options
487
+ packedio << [tag, {}] << [data, {}]
488
+ end
489
+
490
+ def read_packed(packedio, options)
491
+ raise(ArgumentError, 'Missing mandatory option :endian.') unless options.has_key?(:endian)
492
+
493
+ tag = packedio.read([Tag, {:endian => options[:endian]}])
494
+ #STDERR.puts tag.inspect
495
+ data_type = MDTYPE_UNPACK_ARGS[tag.data_type]
496
+
497
+ self.tag = tag
498
+ #STDERR.puts self.tag.inspect
499
+
500
+ raise ElementDataIOError.new(tag, "Unrecognized Matlab type #{tag.raw_data_type}") if data_type.nil?
501
+
502
+ if tag.bytes == 0
503
+ self.data = []
504
+
505
+ else
506
+ number_of_reads = data_type[1].has_key?(:bytes) ? tag.bytes / data_type[1][:bytes] : 1
507
+ data_type[1].merge!({:endian => options[:endian]})
508
+
509
+ if number_of_reads == 1
510
+ self.data = packedio.read(data_type)
511
+
512
+ else
513
+ self.data =
514
+ returning(Array.new) do |ary|
515
+ number_of_reads.times { ary << packedio.read(data_type) }
516
+ end
517
+ end
518
+
519
+ begin
520
+ ignore_padding(packedio, (tag.bytes + tag.size) % 8) unless [:miMATRIX, :miCOMPRESSED].include?(tag.data_type)
521
+
522
+ rescue EOFError
523
+ STDERR.puts self.tag.inspect
524
+ raise(ElementDataIOError.new(tag, "Ignored too much"))
525
+ end
526
+ end
527
+ end
528
+
529
+ def ignore_padding(packedio, bytes)
530
+ if bytes > 0
531
+ #STDERR.puts "Ignored #{8 - bytes} on #{self.tag.data_type}"
532
+ ignored = packedio.read(8 - bytes)
533
+ ignored_unpacked = ignored.unpack("C*")
534
+ raise(IOError, "Nonzero padding detected: #{ignored_unpacked}") if ignored_unpacked.any? { |i| i != 0 }
535
+ end
536
+ end
537
+
538
+ def to_ruby
539
+ data.to_ruby
540
+ end
541
+ end
542
+
543
+ # Doesn't unpack the contents of the element, e.g., if we want to handle
544
+ # manually, or pass the raw string of bytes into NMatrix.
545
+ class RawElement < Element
546
+ def read_packed(packedio, options)
547
+ raise(ArgumentError, 'Missing mandatory option :endian.') unless options.has_key?(:endian)
548
+
549
+ self.tag = packedio.read([Tag, {:endian => options[:endian] }])
550
+ self.data = packedio.read([String, {:endian => options[:endian], :bytes => tag.bytes }])
551
+
552
+ begin
553
+ ignore_padding(packedio, (tag.bytes + tag.size) % 8) unless [:miMATRIX, :miCOMPRESSED].include?(tag.data_type)
554
+
555
+ rescue EOFError
556
+ STDERR.puts self.tag.inspect
557
+ raise ElementDataIOError.new(tag, 'Ignored too much.')
558
+ end
559
+ end
560
+ end
561
+
562
+ #####################
563
+ # End of Mat5Reader #
564
+ #####################
565
+
566
+ end
567
+ end