nmatrix 0.1.0.rc5 → 0.1.0

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/Gemfile +0 -2
  4. data/History.txt +39 -4
  5. data/LICENSE.txt +3 -1
  6. data/Manifest.txt +2 -0
  7. data/README.rdoc +6 -14
  8. data/Rakefile +4 -1
  9. data/ext/nmatrix/data/data.cpp +1 -1
  10. data/ext/nmatrix/data/data.h +2 -1
  11. data/ext/nmatrix/data/rational.h +230 -226
  12. data/ext/nmatrix/extconf.rb +7 -4
  13. data/ext/nmatrix/math.cpp +259 -172
  14. data/ext/nmatrix/math/getri.h +2 -2
  15. data/ext/nmatrix/math/math.h +1 -1
  16. data/ext/nmatrix/ruby_constants.cpp +0 -1
  17. data/ext/nmatrix/ruby_nmatrix.c +55 -32
  18. data/ext/nmatrix/storage/dense/dense.cpp +1 -0
  19. data/ext/nmatrix/storage/yale/yale.cpp +12 -14
  20. data/ext/nmatrix/ttable_helper.rb +0 -1
  21. data/lib/nmatrix.rb +5 -0
  22. data/lib/nmatrix/homogeneous.rb +98 -0
  23. data/lib/nmatrix/io/fortran_format.rb +135 -0
  24. data/lib/nmatrix/io/harwell_boeing.rb +220 -0
  25. data/lib/nmatrix/io/market.rb +18 -8
  26. data/lib/nmatrix/io/mat5_reader.rb +16 -111
  27. data/lib/nmatrix/io/mat_reader.rb +3 -5
  28. data/lib/nmatrix/io/point_cloud.rb +27 -28
  29. data/lib/nmatrix/lapack.rb +3 -1
  30. data/lib/nmatrix/math.rb +112 -43
  31. data/lib/nmatrix/monkeys.rb +67 -11
  32. data/lib/nmatrix/nmatrix.rb +56 -33
  33. data/lib/nmatrix/rspec.rb +2 -2
  34. data/lib/nmatrix/shortcuts.rb +42 -25
  35. data/lib/nmatrix/version.rb +4 -4
  36. data/nmatrix.gemspec +4 -3
  37. data/spec/03_nmatrix_monkeys_spec.rb +72 -0
  38. data/spec/blas_spec.rb +4 -0
  39. data/spec/homogeneous_spec.rb +12 -4
  40. data/spec/io/fortran_format_spec.rb +88 -0
  41. data/spec/io/harwell_boeing_spec.rb +98 -0
  42. data/spec/io/test.rua +9 -0
  43. data/spec/math_spec.rb +51 -9
  44. metadata +38 -9
@@ -30,13 +30,12 @@
30
30
  require 'packable'
31
31
 
32
32
  module NMatrix::IO::Matlab
33
- #
33
+
34
34
  # Class for parsing a .mat file stream.
35
35
  #
36
36
  # The full format of .mat files is available here:
37
37
  # * http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf
38
- #
39
- class MatReader
38
+ class MatReader #:nodoc:
40
39
  MDTYPE_UNPACK_ARGS = {
41
40
  :miINT8 => [Integer, {:signed => true, :bytes => 1}],
42
41
  :miUINT8 => [Integer, {:signed => false, :bytes => 1}],
@@ -146,7 +145,7 @@ module NMatrix::IO::Matlab
146
145
 
147
146
  attr_reader :byte_order
148
147
 
149
- #
148
+
150
149
  # call-seq:
151
150
  # new(stream, options = {}) -> MatReader
152
151
  #
@@ -160,7 +159,6 @@ module NMatrix::IO::Matlab
160
159
  @byte_order = options[:byte_order] || guess_byte_order
161
160
  end
162
161
 
163
- #
164
162
  # call-seq:
165
163
  # guess_byte_order -> Symbol
166
164
  #
@@ -27,15 +27,34 @@
27
27
  #
28
28
  #++
29
29
 
30
- # Reader for a PCD file, specified here:
30
+ # Reader for Point Cloud Data (PCD) file format.
31
31
  #
32
- # * http://pointclouds.org/documentation/tutorials/pcd_file_format.php
32
+ # The documentation of this format can be found in:
33
33
  #
34
- # Note that this does not take the width or height parameters into account.
34
+ # http://pointclouds.org/documentation/tutorials/pcd_file_format.php
35
35
  #
36
+ # Note that this implementation does not take the width or height parameters
37
+ # into account.
36
38
  module NMatrix::IO::PointCloud
37
39
 
38
- class MetaReader
40
+ # For UINT, just add 1 to the index.
41
+ INT_DTYPE_BY_SIZE = [:int8, :int8, :int16, :int32, :int64, :int64] #:nodoc:
42
+ FLOAT_DTYPE_BY_SIZE = {4 => :float32, 8 => :float64} #:nodoc:
43
+
44
+ class << self
45
+ # call-seq:
46
+ # load(filename) -> NMatrix
47
+ #
48
+ # * *Arguments* :
49
+ # - +filename+ -> String giving the name of the file to be loaded.
50
+ #
51
+ # Load a Point Cloud Library PCD file as a matrix.
52
+ def load(filename)
53
+ MetaReader.new(filename).matrix
54
+ end
55
+ end
56
+
57
+ class MetaReader #:nodoc:
39
58
  ENTRIES = [:version, :fields, :size, :type, :count, :width, :height, :viewpoint, :points, :data]
40
59
  ASSIGNS = [:version=, :fields=, :size=, :type=, :count=, :width=, :height=, :viewpoint=, :points=, :data=]
41
60
  CONVERT = [:to_s, :downcase_to_sym, :to_i, :downcase_to_sym, :to_i, :to_i, :to_i, :to_f, :to_i, :downcase_to_sym]
@@ -49,7 +68,7 @@ module NMatrix::IO::PointCloud
49
68
  class << self
50
69
 
51
70
  # Given a type and a number of bytes, figure out an appropriate dtype
52
- def dtype_by_type_and_size t, s #:nodoc:
71
+ def dtype_by_type_and_size t, s
53
72
  if t == :f
54
73
  FLOAT_DTYPE_BY_SIZE[s]
55
74
  elsif t == :u
@@ -104,7 +123,7 @@ module NMatrix::IO::PointCloud
104
123
 
105
124
  protected
106
125
  # Read the current entry of the header.
107
- def read_entry f, entry, assign=nil, convert=nil #:nodoc:
126
+ def read_entry f, entry, assign=nil, convert=nil
108
127
  assign ||= (entry.to_s + "=").to_sym
109
128
 
110
129
  while line = f.gets
@@ -133,7 +152,7 @@ module NMatrix::IO::PointCloud
133
152
 
134
153
  # Determine the dtype for a matrix based on the types and sizes given in the PCD.
135
154
  # Call this only after read_entry has been called.
136
- def dtype #:nodoc:
155
+ def dtype
137
156
  @dtype ||= begin
138
157
  dtypes = self.type.map.with_index do |t,k|
139
158
  MetaReader.dtype_by_type_and_size(t, size[k])
@@ -158,25 +177,5 @@ module NMatrix::IO::PointCloud
158
177
  self.fields.size
159
178
  ]
160
179
  end
161
-
162
- end
163
-
164
- # For UINT, just add 1 to the index.
165
- INT_DTYPE_BY_SIZE = [:int8, :int8, :int16, :int32, :int64, :int64]
166
- FLOAT_DTYPE_BY_SIZE = {4 => :float32, 8 => :float64}
167
-
168
- class << self
169
-
170
- #
171
- # call-seq:
172
- # load(filename) -> NMatrix
173
- #
174
- # * *Arguments* :
175
- # - +filename+ -> String giving the name of the file to be loaded.
176
- #
177
- # Load a Point Cloud Library PCD file as a matrix.
178
- def load(filename)
179
- MetaReader.new(filename).matrix
180
- end
181
180
  end
182
- end
181
+ end
@@ -168,7 +168,9 @@ class NMatrix
168
168
  # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
169
169
  # requires.
170
170
  #
171
- def gesdd(matrix, workspace_size=100000)
171
+ def gesdd(matrix, workspace_size=nil)
172
+ min_workspace_size = matrix.shape.min * (6 + 4 * matrix.shape.min) + matrix.shape.max
173
+ workspace_size = min_workspace_size if workspace_size.nil? || workspace_size < min_workspace_size
172
174
  result = alloc_svd_result(matrix)
173
175
  NMatrix::LAPACK::lapack_gesdd(:a, matrix.shape[0], matrix.shape[1], matrix, matrix.shape[0], result[1], result[0], matrix.shape[0], result[2], matrix.shape[1], workspace_size)
174
176
  result
@@ -30,7 +30,7 @@
30
30
 
31
31
  class NMatrix
32
32
 
33
- module NMMath
33
+ module NMMath #:nodoc:
34
34
  METHODS_ARITY_2 = [:atan2, :ldexp, :hypot]
35
35
  METHODS_ARITY_1 = [:cos, :sin, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,
36
36
  :asinh, :atanh, :exp, :log2, :log10, :sqrt, :cbrt, :erf, :erfc, :gamma, :-@]
@@ -40,48 +40,65 @@ class NMatrix
40
40
  # call-seq:
41
41
  # invert! -> NMatrix
42
42
  #
43
- # Use LAPACK to calculate the inverse of the matrix (in-place). Only works on
44
- # dense matrices.
45
- #
46
- # Note: If you don't have LAPACK, e.g., on a Mac, this may not work yet. Use
47
- # invert instead (which still probably won't work if your matrix is larger than 3x3).
43
+ # Use LAPACK to calculate the inverse of the matrix (in-place) if available.
44
+ # Only works on dense matrices. Alternatively uses in-place Gauss-Jordan
45
+ # elimination.
48
46
  #
49
47
  def invert!
50
- # Get the pivot array; factor the matrix
51
- pivot = self.getrf!
48
+ if NMatrix.has_clapack?
49
+ # Get the pivot array; factor the matrix
50
+ pivot = self.getrf!
52
51
 
53
- # Now calculate the inverse using the pivot array
54
- NMatrix::LAPACK::clapack_getri(:row, self.shape[1], self, self.shape[1], pivot)
52
+ # Now calculate the inverse using the pivot array
53
+ NMatrix::LAPACK::clapack_getri(:row, self.shape[1], self, self.shape[1], pivot)
55
54
 
56
- self
55
+ self
56
+ else
57
+ if self.integer_dtype?
58
+ __inverse__(self.cast(dtype: :rational128), true)
59
+ else
60
+ dtype = self.dtype
61
+ __inverse__(self, true)
62
+ end
63
+ end
57
64
  end
58
65
 
59
66
  #
60
67
  # call-seq:
61
68
  # invert -> NMatrix
62
69
  #
63
- # Make a copy of the matrix, then invert it (requires LAPACK for matrices larger than 3x3).
64
- #
70
+ # Make a copy of the matrix, then invert using Gauss-Jordan elimination.
71
+ # Works without LAPACK.
65
72
  #
66
73
  #
67
74
  # * *Returns* :
68
75
  # - A dense NMatrix.
69
76
  #
70
- def invert
71
- if NMatrix.has_clapack?
72
- begin
73
- self.cast(:dense, self.dtype).invert! # call CLAPACK version
74
- rescue NotImplementedError # probably a rational matrix
75
- inverse = self.clone_structure
76
- __inverse_exact__(inverse)
77
+ def invert lda=nil, ldb=nil
78
+ if lda.nil? and ldb.nil?
79
+ if NMatrix.has_clapack?
80
+ begin
81
+ self.cast(:dense, self.dtype).invert! # call CLAPACK version
82
+ rescue NotImplementedError # probably a rational matrix
83
+ inverse = self.clone
84
+ __inverse__(inverse, false)
85
+ end
86
+ elsif self.integer_dtype? # FIXME: This check is probably too slow.
87
+ rational_self = self.cast(dtype: :rational128)
88
+ inverse = rational_self.clone
89
+ rational_self.__inverse__(inverse, false)
90
+ else
91
+ inverse = self.clone
92
+ __inverse__(inverse, false)
77
93
  end
78
- elsif self.integer_dtype? # FIXME: This check is probably too slow.
79
- rational_self = self.cast(dtype: :rational128)
80
- inverse = rational_self.clone_structure
81
- rational_self.__inverse_exact__(inverse)
82
94
  else
83
- inverse = self.clone_structure
84
- __inverse_exact__(inverse)
95
+ inverse = self.clone_structure
96
+ if self.integer_dtype?
97
+ __inverse_exact__(inverse.cast(dtype: :rational128), lda, ldb)
98
+ else
99
+ dtype = self.dtype
100
+ __inverse_exact__(inverse, lda, ldb)
101
+ end
85
102
  end
86
103
  end
87
104
  alias :inverse :invert
@@ -182,7 +199,7 @@ class NMatrix
182
199
  # gesvd! -> [u, sigma, v_transpose]
183
200
  # gesvd! -> [u, sigma, v_conjugate_transpose] # complex
184
201
  #
185
- # Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
202
+ # Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
186
203
  # This is destructive, modifying the source NMatrix. See also #gesdd.
187
204
  #
188
205
  # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
@@ -219,7 +236,7 @@ class NMatrix
219
236
  # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
220
237
  # requires.
221
238
  #
222
- def gesdd!(workspace_size=1)
239
+ def gesdd!(workspace_size=nil)
223
240
  NMatrix::LAPACK::gesdd(self, workspace_size)
224
241
  end
225
242
 
@@ -234,7 +251,7 @@ class NMatrix
234
251
  # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
235
252
  # requires.
236
253
  #
237
- def gesdd(workspace_size=1)
254
+ def gesdd(workspace_size=nil)
238
255
  self.clone.gesdd!(workspace_size)
239
256
  end
240
257
  #
@@ -360,6 +377,26 @@ class NMatrix
360
377
  end
361
378
  end
362
379
 
380
+ #
381
+ # call-seq:
382
+ # trace -> Numeric
383
+ #
384
+ # Calculates the trace of an nxn matrix.
385
+ #
386
+ # * *Raises* :
387
+ # - +ShapeError+ -> Expected square matrix
388
+ #
389
+ # * *Returns* :
390
+ # - The trace of the matrix (a numeric value)
391
+ #
392
+ def trace
393
+ raise(ShapeError, "Expected square matrix") unless self.shape[0] == self.shape[1] && self.dim == 2
394
+
395
+ (0...self.shape[0]).inject(0) do |total,i|
396
+ total + self[i,i]
397
+ end
398
+ end
399
+
363
400
  ##
364
401
  # call-seq:
365
402
  # mean() -> NMatrix
@@ -518,6 +555,7 @@ class NMatrix
518
555
  #
519
556
  # Return the sum of the contents of the vector. This is the BLAS asum routine.
520
557
  def asum incx=1, n=nil
558
+ return self[0].abs if self.shape == [1]
521
559
  return method_missing(:asum, incx, n) unless vector?
522
560
  NMatrix::BLAS::asum(self, incx, self.size / incx)
523
561
  end
@@ -569,7 +607,8 @@ protected
569
607
 
570
608
  # These don't actually take an argument -- they're called reverse-polish style on the matrix.
571
609
  # This group always gets casted to float64.
572
- [:log, :log2, :log10, :sqrt, :sin, :cos, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh, :asinh, :atanh, :exp, :erf, :erfc, :gamma, :cbrt].each do |ewop|
610
+ [:log, :log2, :log10, :sqrt, :sin, :cos, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,
611
+ :asinh, :atanh, :exp, :erf, :erfc, :gamma, :cbrt].each do |ewop|
573
612
  define_method("__list_unary_#{ewop}__") do
574
613
  self.__list_map_stored__(nil) { |l| Math.send(ewop, l) }.cast(stype, NMatrix.upcast(dtype, :float64))
575
614
  end
@@ -581,7 +620,8 @@ protected
581
620
  end
582
621
  end
583
622
 
584
- # log takes an optional single argument, the base. Default to natural log.
623
+ #:stopdoc:
624
+ # log takes an optional single argument, the base. Default to natural log.
585
625
  def __list_unary_log__(base)
586
626
  self.__list_map_stored__(nil) { |l| Math.log(l, base) }.cast(stype, NMatrix.upcast(dtype, :float64))
587
627
  end
@@ -606,6 +646,35 @@ protected
606
646
  def __dense_unary_negate__
607
647
  self.__dense_map__ { |l| -l }.cast(stype, dtype)
608
648
  end
649
+ #:startdoc:
650
+
651
+ # These are for rounding each value of a matrix
652
+ def __list_unary_round__
653
+ if self.complex_dtype?
654
+ self.__list_map_stored__(nil) { |l| Complex(l.real.round, l.imag.round) }
655
+ .cast(stype, dtype)
656
+ else
657
+ self.__list_map_stored__(nil) { |l| l.round }.cast(stype, dtype)
658
+ end
659
+ end
660
+
661
+ def __yale_unary_round__
662
+ if self.complex_dtype?
663
+ self.__yale_map_stored__ { |l| Complex(l.real.round, l.imag.round) }
664
+ .cast(stype, dtype)
665
+ else
666
+ self.__yale_map_stored__ { |l| l.round }.cast(stype, dtype)
667
+ end
668
+ end
669
+
670
+ def __dense_unary_round__
671
+ if self.complex_dtype?
672
+ self.__dense_map__ { |l| Complex(l.real.round, l.imag.round) }
673
+ .cast(stype, dtype)
674
+ else
675
+ self.__dense_map__ { |l| l.round }.cast(stype, dtype)
676
+ end
677
+ end
609
678
 
610
679
  # These are for calculating the floor or ceil of matrix
611
680
  def dtype_for_floor_or_ceil
@@ -618,36 +687,36 @@ protected
618
687
  return_dtype
619
688
  end
620
689
 
621
- [:floor, :ceil].each do |meth|
690
+ [:floor, :ceil].each do |meth|
622
691
  define_method("__list_unary_#{meth}__") do
623
692
  return_dtype = dtype_for_floor_or_ceil
624
693
 
625
694
  if [:complex64, :complex128].include?(self.dtype)
626
- self.__list_map_stored__(nil) { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
695
+ self.__list_map_stored__(nil) { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
627
696
  else
628
- self.__list_map_stored__(nil) { |l| l.send(meth) }.cast(stype, return_dtype)
697
+ self.__list_map_stored__(nil) { |l| l.send(meth) }.cast(stype, return_dtype)
629
698
  end
630
699
  end
631
-
632
- define_method("__yale_unary_#{meth}__") do
700
+
701
+ define_method("__yale_unary_#{meth}__") do
633
702
  return_dtype = dtype_for_floor_or_ceil
634
703
 
635
704
  if [:complex64, :complex128].include?(self.dtype)
636
- self.__yale_map_stored__ { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
705
+ self.__yale_map_stored__ { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
637
706
  else
638
- self.__yale_map_stored__ { |l| l.send(meth) }.cast(stype, return_dtype)
707
+ self.__yale_map_stored__ { |l| l.send(meth) }.cast(stype, return_dtype)
639
708
  end
640
709
  end
641
-
710
+
642
711
  define_method("__dense_unary_#{meth}__") do
643
712
  return_dtype = dtype_for_floor_or_ceil
644
-
713
+
645
714
  if [:complex64, :complex128].include?(self.dtype)
646
- self.__dense_map__ { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
715
+ self.__dense_map__ { |l| Complex(l.real.send(meth), l.imag.send(meth)) }.cast(stype, return_dtype)
647
716
  else
648
- self.__dense_map__ { |l| l.send(meth) }.cast(stype, return_dtype)
717
+ self.__dense_map__ { |l| l.send(meth) }.cast(stype, return_dtype)
649
718
  end
650
- end
719
+ end
651
720
  end
652
721
 
653
722
  # These take two arguments. One might be a matrix, and one might be a scalar.
@@ -38,19 +38,55 @@ class Array
38
38
  # You must provide a shape for the matrix as the first argument.
39
39
  #
40
40
  # == Arguments:
41
- # <tt>shape</tt> :: Array describing matrix dimensions (or Fixnum for square) -- REQUIRED!
42
- # <tt>dtype</tt> :: Override data type (e.g., to store a Float as :float32 instead of :float64) -- optional.
41
+ # <tt>shape</tt> :: Array describing matrix dimensions (or Fixnum for square).
42
+ # If not provided, will be intuited through #shape.
43
+ # <tt>dtype</tt> :: Override data type (e.g., to store a Float as :float32
44
+ # instead of :float64) -- optional.
43
45
  # <tt>stype</tt> :: Optional storage type (defaults to :dense)
44
- def to_nm(shape, dtype = nil, stype = :dense)
45
- dtype ||=
46
- case self[0]
47
- when Fixnum then :int64
48
- when Float then :float64
49
- when Rational then :rational128
50
- when Complex then :complex128
46
+ def to_nm(shape = nil, dtype = nil, stype = :dense)
47
+ elements = self
48
+
49
+ guess_dtype = ->(type) {
50
+ case type
51
+ when Fixnum then :int64
52
+ when Float then :float64
53
+ when Rational then :rational128
54
+ when Complex then :complex128
55
+ end
56
+ }
57
+
58
+ guess_shape = lambda { |shapey; shape|
59
+ # Get the size of the current dimension
60
+ shape = [shapey.size]
61
+ shape << shapey.map {|s|
62
+ if s.respond_to?(:size) && s.respond_to?(:map)
63
+ guess_shape.call(s)
64
+ else
65
+ nil
66
+ end
67
+ }
68
+ if shape.last.any? {|s| (s != shape.last.first) || s.nil?}
69
+ shape.pop
70
+ end
71
+ if (shape.first != shape.last) && shape.last.all? {|s| s == shape.last.first}
72
+ shape[-1] = shape.last.first
51
73
  end
74
+ shape.flatten
75
+ }
52
76
 
53
- matrix = NMatrix.new(:dense, shape, self, dtype)
77
+ unless shape
78
+ shape = guess_shape.call(self)
79
+ elements.flatten!(shape.size - 1)
80
+ if elements.flatten != elements
81
+ dtype = :object
82
+ else
83
+ dtype ||= guess_dtype[elements[0]]
84
+ end
85
+ end
86
+
87
+ dtype ||= guess_dtype[self[0]]
88
+
89
+ matrix = NMatrix.new(:dense, shape, elements, dtype)
54
90
 
55
91
  if stype != :dense then matrix.cast(stype, dtype) else matrix end
56
92
  end
@@ -64,7 +100,7 @@ class Object #:nodoc:
64
100
  end
65
101
 
66
102
 
67
- module Math
103
+ module Math #:nodoc:
68
104
  class << self
69
105
  NMatrix::NMMath::METHODS_ARITY_2.each do |meth|
70
106
  define_method "nm_#{meth}" do |arg0, arg1|
@@ -82,3 +118,23 @@ module Math
82
118
  end
83
119
  end
84
120
 
121
+ class String
122
+ def underscore
123
+ self.gsub(/::/, '/').
124
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
125
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
126
+ tr("-", "_").
127
+ downcase
128
+ end
129
+ end
130
+
131
+ # Since `autoload` will most likely be deprecated (due to multi-threading concerns),
132
+ # we'll use `const_missing`. See: https://www.ruby-forum.com/topic/3036681 for more info.
133
+ module AutoloadPatch #:nodoc
134
+ def const_missing(name)
135
+ file = name.to_s.underscore
136
+ require "nmatrix/io/#{file}"
137
+ klass = const_get(name)
138
+ return klass if klass
139
+ end
140
+ end