nmatrix 0.1.0.rc5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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