nmatrix 0.1.0.rc1 → 0.1.0.rc2

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.
@@ -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: