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.
- data/.gitignore +27 -0
- data/.rspec +2 -0
- data/Gemfile +3 -5
- data/Guardfile +6 -0
- data/History.txt +33 -0
- data/Manifest.txt +41 -38
- data/README.rdoc +88 -11
- data/Rakefile +35 -53
- data/ext/nmatrix/data/complex.h +372 -0
- data/ext/nmatrix/data/data.cpp +275 -0
- data/ext/nmatrix/data/data.h +707 -0
- data/ext/nmatrix/data/rational.h +421 -0
- data/ext/nmatrix/data/ruby_object.h +446 -0
- data/ext/nmatrix/extconf.rb +101 -51
- data/ext/nmatrix/new_extconf.rb +56 -0
- data/ext/nmatrix/nmatrix.cpp +1609 -0
- data/ext/nmatrix/nmatrix.h +265 -849
- data/ext/nmatrix/ruby_constants.cpp +134 -0
- data/ext/nmatrix/ruby_constants.h +103 -0
- data/ext/nmatrix/storage/common.cpp +70 -0
- data/ext/nmatrix/storage/common.h +170 -0
- data/ext/nmatrix/storage/dense.cpp +665 -0
- data/ext/nmatrix/storage/dense.h +116 -0
- data/ext/nmatrix/storage/list.cpp +1088 -0
- data/ext/nmatrix/storage/list.h +129 -0
- data/ext/nmatrix/storage/storage.cpp +658 -0
- data/ext/nmatrix/storage/storage.h +99 -0
- data/ext/nmatrix/storage/yale.cpp +1601 -0
- data/ext/nmatrix/storage/yale.h +208 -0
- data/ext/nmatrix/ttable_helper.rb +126 -0
- data/ext/nmatrix/{yale/smmp1_header.template.c → types.h} +36 -9
- data/ext/nmatrix/util/io.cpp +295 -0
- data/ext/nmatrix/util/io.h +117 -0
- data/ext/nmatrix/util/lapack.h +1175 -0
- data/ext/nmatrix/util/math.cpp +557 -0
- data/ext/nmatrix/util/math.h +1363 -0
- data/ext/nmatrix/util/sl_list.cpp +475 -0
- data/ext/nmatrix/util/sl_list.h +255 -0
- data/ext/nmatrix/util/util.h +78 -0
- data/lib/nmatrix/blas.rb +70 -0
- data/lib/nmatrix/io/mat5_reader.rb +567 -0
- data/lib/nmatrix/io/mat_reader.rb +162 -0
- data/lib/{string.rb → nmatrix/monkeys.rb} +49 -2
- data/lib/nmatrix/nmatrix.rb +199 -0
- data/lib/nmatrix/nvector.rb +103 -0
- data/lib/nmatrix/version.rb +27 -0
- data/lib/nmatrix.rb +22 -230
- data/nmatrix.gemspec +59 -0
- data/scripts/mac-brew-gcc.sh +47 -0
- data/spec/4x4_sparse.mat +0 -0
- data/spec/4x5_dense.mat +0 -0
- data/spec/blas_spec.rb +47 -0
- data/spec/elementwise_spec.rb +164 -0
- data/spec/io_spec.rb +60 -0
- data/spec/lapack_spec.rb +52 -0
- data/spec/math_spec.rb +96 -0
- data/spec/nmatrix_spec.rb +93 -89
- data/spec/nmatrix_yale_spec.rb +52 -36
- data/spec/nvector_spec.rb +1 -1
- data/spec/slice_spec.rb +257 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/utm5940.mtx +83844 -0
- metadata +113 -71
- data/.autotest +0 -23
- data/.gemtest +0 -0
- data/ext/nmatrix/cblas.c +0 -150
- data/ext/nmatrix/dense/blas_header.template.c +0 -52
- data/ext/nmatrix/dense/elementwise.template.c +0 -107
- data/ext/nmatrix/dense/gemm.template.c +0 -159
- data/ext/nmatrix/dense/gemv.template.c +0 -130
- data/ext/nmatrix/dense/rationalmath.template.c +0 -68
- data/ext/nmatrix/dense.c +0 -307
- data/ext/nmatrix/depend +0 -18
- data/ext/nmatrix/generator/syntax_tree.rb +0 -481
- data/ext/nmatrix/generator.rb +0 -594
- data/ext/nmatrix/list.c +0 -774
- data/ext/nmatrix/nmatrix.c +0 -1977
- data/ext/nmatrix/rational.c +0 -98
- data/ext/nmatrix/yale/complexmath.template.c +0 -71
- data/ext/nmatrix/yale/elementwise.template.c +0 -46
- data/ext/nmatrix/yale/elementwise_op.template.c +0 -73
- data/ext/nmatrix/yale/numbmm.template.c +0 -94
- data/ext/nmatrix/yale/smmp1.template.c +0 -21
- data/ext/nmatrix/yale/smmp2.template.c +0 -43
- data/ext/nmatrix/yale/smmp2_header.template.c +0 -46
- data/ext/nmatrix/yale/sort_columns.template.c +0 -56
- data/ext/nmatrix/yale/symbmm.template.c +0 -54
- data/ext/nmatrix/yale/transp.template.c +0 -68
- data/ext/nmatrix/yale.c +0 -726
- data/lib/array.rb +0 -67
- 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
|