nmatrix 0.1.0.rc1 → 0.1.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -49,6 +49,10 @@ namespace nm { namespace list {
49
49
  * Macros
50
50
  */
51
51
 
52
+ #ifndef RHASH_SET_IFNONE
53
+ #define RHASH_SET_IFNONE(h, v) (RHASH(h)->ifnone = (v))
54
+ #endif
55
+
52
56
  /*
53
57
  * Global Variables
54
58
  */
@@ -580,9 +584,9 @@ extern "C" {
580
584
  static VALUE empty_list_to_hash(const nm::dtype_t dtype, size_t recursions, VALUE default_value) {
581
585
  VALUE h = rb_hash_new();
582
586
  if (recursions) {
583
- RHASH_IFNONE(h) = empty_list_to_hash(dtype, recursions-1, default_value);
587
+ RHASH_SET_IFNONE(h, empty_list_to_hash(dtype, recursions-1, default_value));
584
588
  } else {
585
- RHASH_IFNONE(h) = default_value;
589
+ RHASH_SET_IFNONE(h, default_value);
586
590
  }
587
591
  return h;
588
592
  }
@@ -0,0 +1,182 @@
1
+ #--
2
+ # = NMatrix
3
+ #
4
+ # A linear algebra library for scientific computation in Ruby.
5
+ # NMatrix is part of SciRuby.
6
+ #
7
+ # NMatrix was originally inspired by and derived from NArray, by
8
+ # Masahiro Tanaka: http://narray.rubyforge.org
9
+ #
10
+ # == Copyright Information
11
+ #
12
+ # SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
14
+ #
15
+ # Please see LICENSE.txt for additional copyright notices.
16
+ #
17
+ # == Contributing
18
+ #
19
+ # By contributing source code to SciRuby, you agree to be bound by
20
+ # our Contributor Agreement:
21
+ #
22
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
23
+ #
24
+ # == io/point_cloud.rb
25
+ #
26
+ # Point Cloud Library (PCL) PCD file IO functions.
27
+ #
28
+ #++
29
+
30
+ # Reader for a PCD file, specified here:
31
+ #
32
+ # * http://pointclouds.org/documentation/tutorials/pcd_file_format.php
33
+ #
34
+ # Note that this does not take the width or height parameters into account.
35
+ #
36
+ module NMatrix::IO::PointCloud
37
+
38
+ class MetaReader
39
+ ENTRIES = [:version, :fields, :size, :type, :count, :width, :height, :viewpoint, :points, :data]
40
+ ASSIGNS = [:version=, :fields=, :size=, :type=, :count=, :width=, :height=, :viewpoint=, :points=, :data=]
41
+ CONVERT = [:to_s, :downcase_to_sym, :to_i, :downcase_to_sym, :to_i, :to_i, :to_i, :to_f, :to_i, :downcase_to_sym]
42
+
43
+ DTYPE_CONVERT = {:byte => :to_i, :int8 => :to_i, :int16 => :to_i, :int32 => :to_i, :float32 => :to_f, :float64 => :to_f}
44
+
45
+ # For UINT, just add 1 to the index.
46
+ INT_DTYPE_BY_SIZE = {1 => :int8, 2 => :int16, 4 => :int32, 8 => :int64, 16 => :int64}
47
+ FLOAT_DTYPE_BY_SIZE = {1 => :float32, 2 => :float32, 4 => :float32, 8 => :float64,16 => :float64}
48
+
49
+ class << self
50
+
51
+ # Given a type and a number of bytes, figure out an appropriate dtype
52
+ def dtype_by_type_and_size t, s #:nodoc:
53
+ if t == :f
54
+ FLOAT_DTYPE_BY_SIZE[s]
55
+ elsif t == :u
56
+ return :byte if s == 1
57
+ INT_DTYPE_BY_SIZE[s*2]
58
+ else
59
+ INT_DTYPE_BY_SIZE[s]
60
+ end
61
+ end
62
+ end
63
+
64
+ # call-seq:
65
+ # PointCloudReader::MetaReader.new(filename) -> MetaReader
66
+ #
67
+ # * *Arguments* :
68
+ # - +filename+ -> String giving the name of the file to be loaded.
69
+ # * *Raises* :
70
+ # - +NotImplementedError+ -> only ASCII supported currently
71
+ # - +IOError+ -> premature end of file
72
+ #
73
+ # Open a file and read the metadata at the top; then read the PCD into an
74
+ # NMatrix.
75
+ #
76
+ # In addition to the fields in the PCD file, there will be at least one
77
+ # additional attribute, :matrix, storing the data.
78
+ def initialize filename
79
+ f = File.new(filename, "r")
80
+
81
+ ENTRIES.each.with_index do |entry,i|
82
+ read_entry(f, entry, ASSIGNS[i], CONVERT[i])
83
+ end
84
+
85
+ raise(NotImplementedError, "only ASCII supported currently") unless self.data.first == :ascii
86
+
87
+ @matrix = NMatrix.new(self.shape, dtype: self.dtype)
88
+
89
+ # Do we want to use to_i or to_f?
90
+ convert = DTYPE_CONVERT[self.dtype]
91
+
92
+ i = 0
93
+ while line = f.gets
94
+ @matrix[i,:*] = line.chomp.split.map { |f| f.send(convert) }
95
+ i += 1
96
+ end
97
+
98
+ raise(IOError, "premature end of file") if i < self.points[0]
99
+
100
+ end
101
+
102
+ attr_accessor *ENTRIES
103
+ attr_reader :matrix
104
+
105
+ protected
106
+ # Read the current entry of the header.
107
+ def read_entry f, entry, assign=nil, convert=nil #:nodoc:
108
+ assign ||= (entry.to_s + "=").to_sym
109
+
110
+ while line = f.gets
111
+ next if line =~ /^\s*#/ # ignore comment lines
112
+ line = line.chomp.split(/\s*#/)[0] # ignore the comments after any data
113
+
114
+ # Split, remove the entry name, and convert to the correct type.
115
+ self.send(assign,
116
+ line.split.tap { |t| t.shift }.map do |f|
117
+ if convert.nil?
118
+ f
119
+ elsif convert == :downcase_to_sym
120
+ f.downcase.to_sym
121
+ else
122
+ f.send(convert)
123
+ end
124
+ end)
125
+
126
+ # We don't really want to loop.
127
+ break
128
+ end
129
+
130
+ self.send(entry)
131
+ end
132
+
133
+
134
+ # Determine the dtype for a matrix based on the types and sizes given in the PCD.
135
+ # Call this only after read_entry has been called.
136
+ def dtype #:nodoc:
137
+ @dtype ||= begin
138
+ dtypes = self.type.map.with_index do |t,k|
139
+ MetaReader.dtype_by_type_and_size(t, size[k])
140
+ end.sort.uniq
141
+
142
+ # This could probably save one comparison at most, but we assume that
143
+ # worst case isn't going to happen very often.
144
+ while dtypes.size > 1
145
+ d = NMatrix.upcast(dtypes[0], dtypes[1])
146
+ dtypes.shift
147
+ dtypes[0] = d
148
+ end
149
+
150
+ dtypes[0]
151
+ end
152
+ end
153
+
154
+ # Determine the shape of the matrix.
155
+ def shape
156
+ @shape ||= [
157
+ self.points[0],
158
+ self.fields.size
159
+ ]
160
+ 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
+ end
182
+ end
@@ -33,7 +33,7 @@ class NMatrix
33
33
  module NMMath
34
34
  METHODS_ARITY_2 = [:atan2, :ldexp, :hypot]
35
35
  METHODS_ARITY_1 = [:cos, :sin, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,
36
- :asinh, :atanh, :exp, :log2, :log10, :sqrt, :cbrt, :erf, :erfc, :gamma]
36
+ :asinh, :atanh, :exp, :log2, :log10, :sqrt, :cbrt, :erf, :erfc, :gamma, :-@]
37
37
  end
38
38
 
39
39
  #
@@ -43,7 +43,8 @@ class NMatrix
43
43
  # Use LAPACK to calculate the inverse of the matrix (in-place). Only works on
44
44
  # dense matrices.
45
45
  #
46
- # Note: If you don't have LAPACK, e.g., on a Mac, this may not work yet.
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).
47
48
  #
48
49
  def invert!
49
50
  # Get the pivot array; factor the matrix
@@ -59,13 +60,29 @@ class NMatrix
59
60
  # call-seq:
60
61
  # invert -> NMatrix
61
62
  #
62
- # Make a copy of the matrix, then invert it (requires LAPACK).
63
+ # Make a copy of the matrix, then invert it (requires LAPACK for matrices larger than 3x3).
64
+ #
65
+ #
63
66
  #
64
67
  # * *Returns* :
65
68
  # - A dense NMatrix.
66
69
  #
67
70
  def invert
68
- self.cast(:dense, self.dtype).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
+ 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
+ else
83
+ inverse = self.clone_structure
84
+ __inverse_exact__(inverse)
85
+ end
69
86
  end
70
87
  alias :inverse :invert
71
88
 
@@ -552,7 +569,7 @@ protected
552
569
 
553
570
  # These don't actually take an argument -- they're called reverse-polish style on the matrix.
554
571
  # This group always gets casted to float64.
555
- [:log2, :log10, :sqrt, :sin, :cos, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh, :asinh, :atanh, :exp, :erf, :erfc, :gamma, :cbrt].each do |ewop|
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|
556
573
  define_method("__list_unary_#{ewop}__") do
557
574
  self.__list_map_stored__(nil) { |l| Math.send(ewop, l) }.cast(stype, NMatrix.upcast(dtype, :float64))
558
575
  end
@@ -577,6 +594,19 @@ protected
577
594
  self.__dense_map__ { |l| Math.log(l, base) }.cast(stype, NMatrix.upcast(dtype, :float64))
578
595
  end
579
596
 
597
+ # These are for negating matrix contents using -@
598
+ def __list_unary_negate__
599
+ self.__list_map_stored__(nil) { |l| -l }.cast(stype, dtype)
600
+ end
601
+
602
+ def __yale_unary_negate__
603
+ self.__yale_map_stored__ { |l| -l }.cast(stype, dtype)
604
+ end
605
+
606
+ def __dense_unary_negate__
607
+ self.__dense_map__ { |l| -l }.cast(stype, dtype)
608
+ end
609
+
580
610
  # These take two arguments. One might be a matrix, and one might be a scalar.
581
611
  # See also monkeys.rb, which contains Math module patches to let the first
582
612
  # arg be a scalar
@@ -49,24 +49,37 @@ class NMatrix
49
49
  autoload :Mat5Reader, 'nmatrix/io/mat5_reader'
50
50
  end
51
51
 
52
- # FIXME: Remove autoloads
53
- autoload :Market, 'nmatrix/io/market'
52
+ autoload :Market, 'nmatrix/io/market.rb'
53
+ autoload :PointCloud, 'nmatrix/io/point_cloud.rb'
54
54
  end
55
55
 
56
56
  class << self
57
57
  #
58
58
  # call-seq:
59
- # load_file(path) -> Mat5Reader
59
+ # load_matlab_file(path) -> Mat5Reader
60
60
  #
61
61
  # * *Arguments* :
62
- # - +path+ -> The path to a version 5 .mat file.
62
+ # - +file_path+ -> The path to a version 5 .mat file.
63
63
  # * *Returns* :
64
64
  # - A Mat5Reader object.
65
65
  #
66
- def load_file(file_path)
66
+ def load_matlab_file(file_path)
67
67
  NMatrix::IO::Mat5Reader.new(File.open(file_path, 'rb')).to_ruby
68
68
  end
69
69
 
70
+ #
71
+ # call-seq:
72
+ # load_pcd_file(path) -> PointCloudReader::MetaReader
73
+ #
74
+ # * *Arguments* :
75
+ # - +file_path+ -> The path to a PCL PCD file.
76
+ # * *Returns* :
77
+ # - A PointCloudReader::MetaReader object with the matrix stored in its +matrix+ property
78
+ #
79
+ def load_pcd_file(file_path)
80
+ NMatrix::IO::PointCloudReader::MetaReader.new(file_path)
81
+ end
82
+
70
83
  #
71
84
  # Calculate the size of an NMatrix of a given shape.
72
85
  def size(shape)
@@ -226,7 +239,7 @@ class NMatrix
226
239
 
227
240
 
228
241
  ##
229
- # call-seq:
242
+ # call-seq:
230
243
  # integer_dtype?() -> Boolean
231
244
  #
232
245
  # Checks if dtype is an integer type
@@ -403,15 +416,45 @@ class NMatrix
403
416
  # * *Returns* :
404
417
  # - A copy with a different shape.
405
418
  #
406
- def reshape new_shape
407
- t = reshape_clone_structure(new_shape)
408
- left_params = [:*]*new_shape.size
419
+ def reshape new_shape,*shapes
420
+ if new_shape.is_a?Fixnum
421
+ newer_shape = [new_shape]+shapes
422
+ else # new_shape is an Array
423
+ newer_shape = new_shape
424
+ end
425
+ t = reshape_clone_structure(newer_shape)
426
+ left_params = [:*]*newer_shape.size
427
+ puts(left_params)
409
428
  right_params = [:*]*self.shape.size
410
429
  t[*left_params] = self[*right_params]
411
430
  t
412
431
  end
413
432
 
414
433
 
434
+ #
435
+ # call-seq:
436
+ # reshape!(new_shape) -> NMatrix
437
+ # reshape! new_shape -> NMatrix
438
+ #
439
+ # Reshapes the matrix (in-place) to the desired shape. Note that this function does not do a resize; the product of
440
+ # the new and old shapes' components must be equal.
441
+ #
442
+ # * *Arguments* :
443
+ # - +new_shape+ -> Array of positive Fixnums.
444
+ #
445
+ def reshape! new_shape,*shapes
446
+ if self.is_ref?
447
+ raise(ArgumentError, "This operation cannot be performed on reference slices")
448
+ else
449
+ if new_shape.is_a?Fixnum
450
+ shape = [new_shape]+shapes
451
+ else # new_shape is an Array
452
+ shape = new_shape
453
+ end
454
+ self.reshape_bang(shape)
455
+ end
456
+ end
457
+
415
458
  #
416
459
  # call-seq:
417
460
  # transpose -> NMatrix
@@ -427,7 +470,9 @@ class NMatrix
427
470
  # - A copy of the matrix, but transposed.
428
471
  #
429
472
  def transpose(permute = nil)
430
- if self.dim <= 2 # This will give an error if dim is 1.
473
+ if self.dim == 1
474
+ return self.clone
475
+ elsif self.dim == 2
431
476
  new_shape = [self.shape[1], self.shape[0]]
432
477
  elsif permute.nil?
433
478
  raise(ArgumentError, "need permutation array of size #{self.dim}")
@@ -771,6 +816,21 @@ class NMatrix
771
816
  end
772
817
 
773
818
 
819
+ #
820
+ # call-seq:
821
+ # clone_structure -> NMatrix
822
+ #
823
+ # This function is like clone, but it only copies the structure and the default value.
824
+ # None of the other values are copied. It takes an optional capacity argument. This is
825
+ # mostly only useful for dense, where you may not want to initialize; for other types,
826
+ # you should probably use +zeros_like+.
827
+ #
828
+ def clone_structure(capacity = nil)
829
+ opts = {stype: self.stype, default: self.default_value, dtype: self.dtype}
830
+ opts = {capacity: capacity}.merge(opts) if self.yale?
831
+ NMatrix.new(self.shape, opts)
832
+ end
833
+
774
834
  # This is how you write an individual element-wise operation function:
775
835
  #def __list_elementwise_add__ rhs
776
836
  # self.__list_map_merged_stored__(rhs){ |l,r| l+r }.cast(self.stype, NMatrix.upcast(self.dtype, rhs.dtype))
@@ -797,22 +857,6 @@ protected
797
857
  end
798
858
 
799
859
 
800
- #
801
- # call-seq:
802
- # clone_structure -> NMatrix
803
- #
804
- # This function is like clone, but it only copies the structure and the default value.
805
- # None of the other values are copied. It takes an optional capacity argument. This is
806
- # mostly only useful for dense, where you may not want to initialize; for other types,
807
- # you should probably use +zeros_like+.
808
- #
809
- def clone_structure(capacity = nil)
810
- opts = {stype: self.stype, default: self.default_value, dtype: self.dtype}
811
- opts = {capacity: capacity}.merge(opts) if self.yale?
812
- NMatrix.new(self.shape, opts)
813
- end
814
-
815
-
816
860
  # Clone the structure as needed for a reshape
817
861
  def reshape_clone_structure(new_shape) #:nodoc:
818
862
  raise(ArgumentError, "reshape cannot resize; size of new and old matrices must match") unless self.size == new_shape.inject(1) { |p,i| p *= i }
@@ -263,6 +263,7 @@ class NMatrix
263
263
  alias :diag :diagonal
264
264
  alias :diagonals :diagonal
265
265
 
266
+
266
267
  #
267
268
  # call-seq:
268
269
  # random(shape) -> NMatrix
@@ -270,6 +271,10 @@ class NMatrix
270
271
  # Creates a +:dense+ NMatrix with random numbers between 0 and 1 generated
271
272
  # by +Random::rand+. The parameter is the dimension of the matrix.
272
273
  #
274
+ # If you use an integer dtype, make sure to specify :scale as a parameter, or you'll
275
+ # only get a matrix of 0s. You may not currently generate random numbers for
276
+ # a rational matrix.
277
+ #
273
278
  # * *Arguments* :
274
279
  # - +shape+ -> Array (or integer for square matrix) specifying the dimensions.
275
280
  # * *Returns* :
@@ -280,16 +285,28 @@ class NMatrix
280
285
  # NMatrix.random([2, 2]) # => 0.4859439730644226 0.1783195585012436
281
286
  # 0.23193766176700592 0.4503345191478729
282
287
  #
288
+ # NMatrix.random([2, 2], :dtype => :byte, :scale => 255) # => [ [252, 108] [44, 12] ]
289
+ #
283
290
  def random(shape, opts={})
291
+ scale = opts.delete(:scale) || 1.0
292
+
293
+ raise(NotImplementedError, "does not support rational random number generation") if opts[:dtype].to_s =~ /^rational/
294
+
284
295
  rng = Random.new
285
296
 
286
297
  random_values = []
287
298
 
299
+
288
300
  # Construct the values of the final matrix based on the dimension.
289
- NMatrix.size(shape).times { |i| random_values << rng.rand }
301
+ if opts[:dtype] == :complex64 || opts[:dtype] == :complex128
302
+ NMatrix.size(shape).times { |i| random_values << Complex(rng.rand(scale), rng.rand(scale)) }
303
+ else
304
+ NMatrix.size(shape).times { |i| random_values << rng.rand(scale) }
305
+ end
290
306
 
291
307
  NMatrix.new(shape, random_values, {:dtype => :float64, :stype => :dense}.merge(opts))
292
308
  end
309
+ alias :rand :random
293
310
 
294
311
  #
295
312
  # call-seq: