Class: NMatrix::IO::Matlab::Mat5Reader::MatrixData

Inherits:
MatrixDataStruct show all
Includes:
Packable
Defined in:
lib/nmatrix/io/mat5_reader.rb

Overview

:nodoc:

Instance Attribute Summary

Attributes inherited from MatrixDataStruct

#cells, #column_index, #complex, #dimensions, #global, #imaginary_part, #logical, #matlab_class, #matlab_name, #nonzero_max, #real_part, #row_index

Instance Method Summary collapse

Instance Method Details

#guess_dtype_from_mdtypeObject

call-seq:

guess_dtype_from_mdtype -> Symbol

Try to determine what dtype and such to use.

TODO: Needs to be verified that unsigned MATLAB types are being converted to the correct NMatrix signed dtypes.


136
137
138
139
140
141
142
# File 'lib/nmatrix/io/mat5_reader.rb', line 136

def guess_dtype_from_mdtype
  dtype = MatReader::MDTYPE_TO_DTYPE[self.real_part.tag.data_type]

  return dtype unless self.complex

  dtype == :float32 ? :complex64 : :complex128
end

#ignore_padding(packedio, bytes) ⇒ Object


347
348
349
350
# File 'lib/nmatrix/io/mat5_reader.rb', line 347

def ignore_padding(packedio, bytes)
  packedio.read([Integer, {:unsigned => true, \
   :bytes => bytes}]) if bytes > 0
end

#read_packed(packedio, options) ⇒ Object


297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/nmatrix/io/mat5_reader.rb', line 297

def read_packed(packedio, options)
  flags_class, self.nonzero_max = packedio.read([Element, options]).data

  self.matlab_class   = MatReader::MCLASSES[flags_class % 16]

  self.logical        = (flags_class >> 8) % 2 == 1 ? true : false
  self.global         = (flags_class >> 9) % 2 == 1 ? true : false
  self.complex        = (flags_class >> 10) % 2 == 1 ? true : false

  dimensions_tag_data = packedio.read([Element, options])
  self.dimensions     = dimensions_tag_data.data

  begin
    name_tag_data   = packedio.read([Element, options])
    self.matlab_name = name_tag_data.data.is_a?(Array) ? \
     name_tag_data.data.collect { |i| i.chr }.join('') : \
     name_tag_data.data.chr

  rescue ElementDataIOError => e
    STDERR.puts "ERROR: Failure while trying to read Matlab variable name: #{name_tag_data.inspect}"
    STDERR.puts 'Element Tag:'
    STDERR.puts "    #{e.tag}"
    STDERR.puts 'Previously, I read these dimensions:'
    STDERR.puts "    #{dimensions_tag_data.inspect}"
    STDERR.puts "Unpack options were: #{options.inspect}"
    raise(e)
  end

  if self.matlab_class == :mxCELL
    # Read what may be a series of matrices
    self.cells = []
    STDERR.puts("Warning: Cell array does not yet support reading multiple dimensions") if dimensions.size > 2 || (dimensions[0] > 1 && dimensions[1] > 1)
    number_of_cells = dimensions.inject(1) { |prod,i| prod * i }
    number_of_cells.times { self.cells << \
     packedio.read([Element, options]) }

  else
    read_opts = [RawElement, {:bytes => options[:bytes], \
     :endian => :native}]

    if self.matlab_class == :mxSPARSE
      self.column_index = packedio.read(read_opts)
      self.row_index    = packedio.read(read_opts)
    end

    self.real_part   = packedio.read(read_opts)
    self.imaginary_part = packedio.read(read_opts) if self.complex
  end
end

#repacked_data(to_dtype = nil) ⇒ Object

Unpacks and repacks data into the appropriate format for NMatrix.

If data is already in the appropriate format, does not unpack or repack, just returns directly.

Complex is always unpacked and repacked, as the real and imaginary components must be merged together (MATLAB stores them separately for some crazy reason).

Used only for Yale storage creation. For dense, see unpacked_data.

This function calls repack and complex_merge, which are both defined in io.cpp.


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/nmatrix/io/mat5_reader.rb', line 187

def repacked_data(to_dtype = nil)

  real_mdtype = self.real_part.tag.data_type

  # Figure out what dtype to use based on the MATLAB data-types
  # (mdtypes). They could be different for real and imaginary, so call
  # upcast to figure out what to use.

  components = [] # real and imaginary parts or just the real part

  if self.complex
    imag_mdtype = self.imaginary_part.tag.data_type

    # Make sure we convert both mdtypes do the same dtype
    to_dtype ||= NMatrix.upcast(MatReader::MDTYPE_TO_DTYPE[real_mdtype], \
     MatReader::MDTYPE_TO_DTYPE[imag_mdtype])

    # Let's make sure we don't try to send NMatrix complex integers.
    #  We need complex floating points.
    unless [:float32, :float64].include?(to_dtype)
      to_dtype = NMatrix.upcast(to_dtype, :float32)
    end

    STDERR.puts "imag: Requesting dtype #{to_dtype.inspect}"
    # Repack the imaginary part
    components[1] = ::NMatrix::IO::Matlab.repack( self.imaginary_part.data, \
     imag_mdtype, :dtype => to_dtype )

  else

    to_dtype ||= MatReader::MDTYPE_TO_DTYPE[real_mdtype]

    # Sometimes repacking isn't necessary -- sometimes the format is already good
    if MatReader::NO_REPACK.include?(real_mdtype)
      STDERR.puts "No repack"
      return [self.real_part.data, to_dtype]
    end

  end

  # Repack the real part
  STDERR.puts "real: Requesting dtype #{to_dtype.inspect}"
  components[0] = ::NMatrix::IO::Matlab.repack( \
   self.real_part.data, real_mdtype, :dtype => to_dtype )

  # Merge the two parts if complex, or just return the real part.
  [self.complex ? ::NMatrix::IO::Matlab.complex_merge( \
   components[0], components[1], to_dtype ) : components[0],
   to_dtype]
end

#repacked_indicesObject

Unpacks and repacks index data into the appropriate format for NMatrix.

If data is already in the appropriate format, does not unpack or repack, just returns directly.


242
243
244
245
246
247
248
249
# File 'lib/nmatrix/io/mat5_reader.rb', line 242

def repacked_indices
  repacked_row_indices = ::NMatrix::IO::Matlab.repack( \
   self.row_index.data, :miINT32, :itype )
  repacked_col_indices = ::NMatrix::IO::Matlab.repack( \
   self.column_index.data, :miINT32, :itype )

  [repacked_row_indices, repacked_col_indices]
end

#to_nm(dtype = nil) ⇒ Object

call-seq:

to_nm(dtype = nil) -> NMatrix

Create an NMatrix from a MATLAB .mat (v5) matrix.

This function matches the storage type exactly. That is, a regular matrix in MATLAB will be a dense NMatrix, and a sparse (old Yale) one in MATLAB will be a :yale (new Yale) matrix in NMatrix.

Note that NMatrix has no old Yale type, so this uses a semi-hidden version of the NMatrix constructor to pass in — as directly as possible – the stored bytes in a MATLAB sparse matrix. This constructor should also be used for other IO formats that want to create sparse matrices from IA and JA vectors (e.g., SciPy).

This is probably not the fastest code. An ideal solution would be a C plugin of some sort for reading the MATLAB .mat file. However, .mat v5 is a really complicated format, and lends itself to an object-oriented solution.


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/nmatrix/io/mat5_reader.rb', line 272

def to_nm(dtype = nil)
  # Hardest part is figuring out from_dtype, from_index_dtype, and dtype.
  dtype   ||= guess_dtype_from_mdtype
  from_dtype = MatReader::MDTYPE_TO_DTYPE[self.real_part.tag.data_type]

  # Create the same kind of matrix that MATLAB saved.
  case matlab_class
  when :mxSPARSE
    raise(NotImplementedError, "expected .mat row indices to be of type :miINT32") unless row_index.tag.data_type == :miINT32
    raise(NotImplementedError, "expected .mat column indices to be of type :miINT32") unless column_index.tag.data_type == :miINT32
    #require 'pry'
    #binding.pry

    # MATLAB always uses :miINT32 for indices according to the spec
    ia_ja                     = repacked_indices
    data_str, repacked_dtype  = repacked_data(dtype)
    NMatrix.new(:yale, self.dimensions.reverse, repacked_dtype, \
     ia_ja[0], ia_ja[1], data_str, repacked_dtype)

  else
    # Call regular dense constructor.
    NMatrix.new(:dense, self.dimensions.reverse, unpacked_data, dtype).transpose
  end
end

#to_rubyObject

call-seq:

to_ruby -> NMatrix
to_ruby -> Array

Figure out the appropriate Ruby type to convert to, and do it. There are basically two possible types: NMatrix and Array. This method is recursive, so an Array is going to contain other Arrays and/or NMatrix objects.

mxCELL types (cells) will be converted to the Array type.

mxSPARSE and other types will be converted to NMatrix, with the appropriate stype (:yale or :dense, respectively).

See also to_nm, which is responsible for NMatrix instantiation.


121
122
123
124
125
126
127
# File 'lib/nmatrix/io/mat5_reader.rb', line 121

def to_ruby
  case matlab_class
  when :mxSPARSE then return to_nm
  when :mxCELL  then return self.cells.collect { |c| c.to_ruby }
  else         return to_nm
  end
end

#unpacked_data(real_mdtype = nil, imag_mdtype = nil) ⇒ Object

call-seq:

unpacked_data(real_mdtype = nil, imag_mdtype = nil) ->

Unpacks data without repacking it.

Used only for dense matrix creation. Yale matrix creation uses repacked_data.


153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/nmatrix/io/mat5_reader.rb', line 153

def unpacked_data(real_mdtype = nil, imag_mdtype = nil)
  # Get Matlab data type and unpack args
  real_mdtype ||= self.real_part.tag.data_type
  real_unpack_args = MatReader::MDTYPE_UNPACK_ARGS[real_mdtype]

  # zip real and complex components together, or just return real component
  if self.complex
    imag_mdtype ||= self.imaginary_part.tag.data_type
    imag_unpack_args = MatReader::MDTYPE_UNPACK_ARGS[imag_mdtype]

    unpacked_real = self.real_part.data.unpack(real_unpack_args)
    unpacked_imag = self.imaginary_part.data.unpack(imag_unpack_args)

    unpacked_real.zip(unpacked_imag).flatten
  else
    length = self.dimensions.inject(1) { |a,b| a * b } # get the product
    self.real_part.data.unpack(*(real_unpack_args*length))
  end

end

#write_packed(packedio, options) ⇒ Object

Raises:

  • (NotImplementedError)

101
102
103
104
# File 'lib/nmatrix/io/mat5_reader.rb', line 101

def write_packed(packedio, options)
  raise NotImplementedError
  packedio << [info, {:bytes => padded_bytes}.merge(options)]
end