nmatrix 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/History.txt +102 -10
  3. data/README.rdoc +24 -32
  4. data/Rakefile +1 -1
  5. data/ext/nmatrix/data/complex.h +9 -0
  6. data/ext/nmatrix/data/data.cpp +78 -4
  7. data/ext/nmatrix/data/data.h +86 -54
  8. data/ext/nmatrix/data/rational.h +2 -0
  9. data/ext/nmatrix/data/ruby_object.h +38 -8
  10. data/ext/nmatrix/extconf.rb +13 -7
  11. data/ext/nmatrix/nmatrix.cpp +262 -139
  12. data/ext/nmatrix/nmatrix.h +11 -4
  13. data/ext/nmatrix/storage/common.cpp +20 -13
  14. data/ext/nmatrix/storage/common.h +18 -12
  15. data/ext/nmatrix/storage/dense.cpp +122 -192
  16. data/ext/nmatrix/storage/dense.h +4 -2
  17. data/ext/nmatrix/storage/list.cpp +467 -636
  18. data/ext/nmatrix/storage/list.h +6 -3
  19. data/ext/nmatrix/storage/storage.cpp +83 -46
  20. data/ext/nmatrix/storage/storage.h +7 -7
  21. data/ext/nmatrix/storage/yale.cpp +621 -361
  22. data/ext/nmatrix/storage/yale.h +21 -9
  23. data/ext/nmatrix/ttable_helper.rb +27 -31
  24. data/ext/nmatrix/types.h +1 -1
  25. data/ext/nmatrix/util/math.cpp +9 -10
  26. data/ext/nmatrix/util/sl_list.cpp +1 -7
  27. data/ext/nmatrix/util/sl_list.h +0 -118
  28. data/lib/nmatrix/blas.rb +59 -18
  29. data/lib/nmatrix/monkeys.rb +0 -52
  30. data/lib/nmatrix/nmatrix.rb +136 -9
  31. data/lib/nmatrix/nvector.rb +33 -0
  32. data/lib/nmatrix/shortcuts.rb +95 -16
  33. data/lib/nmatrix/version.rb +1 -1
  34. data/lib/nmatrix/yale_functions.rb +25 -19
  35. data/spec/blas_spec.rb +1 -19
  36. data/spec/elementwise_spec.rb +132 -17
  37. data/spec/lapack_spec.rb +0 -3
  38. data/spec/nmatrix_list_spec.rb +18 -0
  39. data/spec/nmatrix_spec.rb +44 -18
  40. data/spec/nmatrix_yale_spec.rb +1 -3
  41. data/spec/shortcuts_spec.rb +26 -36
  42. data/spec/slice_spec.rb +2 -4
  43. metadata +2 -2
@@ -52,18 +52,6 @@ class Array
52
52
 
53
53
  if stype != :dense then matrix.cast(stype, dtype) else matrix end
54
54
  end
55
-
56
- unless method_defined?(:max)
57
- def max #:nodoc:
58
- self.inject(self.first) { |m, n| if n > m then n else m end }
59
- end
60
- end
61
-
62
- unless method_defined?(:min)
63
- def min #:nodoc:
64
- self.inject(self.first) { |m, n| if n < m then n else m end }
65
- end
66
- end
67
55
  end
68
56
 
69
57
  class Object #:nodoc:
@@ -72,43 +60,3 @@ class Object #:nodoc:
72
60
  value
73
61
  end
74
62
  end
75
-
76
- class String #:nodoc:
77
- unless method_defined?(:constantize)
78
- # Based on constantize from ActiveSupport::Inflector
79
- def constantize
80
- names = self.split('::')
81
- names.shift if names.empty? || names.first.empty?
82
-
83
- constant = Object
84
- names.each do |name|
85
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
86
- end
87
- constant
88
- end
89
- end
90
-
91
- unless method_defined?(:camelize)
92
- # Adapted from camelize from ActiveSupport::Inflector
93
- def camelize first_letter_in_uppercase = true
94
- if first_letter_in_uppercase
95
- self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
96
- else
97
- self.to_s[0].chr.downcase + self[1..-1].camelize
98
- end
99
- end
100
- end
101
-
102
- unless method_defined?(:underscore)
103
- # Adapted from underscore from ActiveSupport::Inflector
104
- def underscore
105
- word = self.dup
106
- word.gsub!(/::/, '/')
107
- word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
108
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
109
- word.tr!("-", "_")
110
- word.downcase!
111
- word
112
- end
113
- end
114
- end
@@ -82,6 +82,48 @@ class NMatrix
82
82
  end
83
83
  alias :pp :pretty_print
84
84
 
85
+
86
+ #
87
+ # call-seq:
88
+ # cast(stype, dtype, default) -> NMatrix
89
+ # cast(stype, dtype) -> NMatrix
90
+ # cast(stype) -> NMatrix
91
+ # cast(options) -> NMatrix
92
+ #
93
+ # This is a user-friendly helper for calling #cast_full. The easiest way to call this function is using an
94
+ # options hash, e.g.,
95
+ #
96
+ # n.cast(:stype => :yale, :dtype => :int64, :default => false)
97
+ #
98
+ # For list and yale, :default sets the "default value" or "init" of the matrix. List allows a bit more freedom
99
+ # since non-zeros are permitted. For yale, unpredictable behavior may result if the value is not false, nil, or
100
+ # some version of 0. Dense discards :default.
101
+ #
102
+ # dtype and stype are inferred from the matrix upon which #cast is called -- so you only really need to provide
103
+ # one. You can actually call this function with no arguments, in which case it functions like #clone.
104
+ #
105
+ # If your dtype is :object and you are converting from :dense to a sparse type, it is recommended that you
106
+ # provide a :default, as 0 may behave differently from its Float, Rational, or Complex equivalent. If no option
107
+ # is given, Fixnum 0 will be used.
108
+ def cast(*params)
109
+ if (params.size > 0 && params[0].is_a?(Hash))
110
+ opts = {
111
+ :stype => self.stype,
112
+ :dtype => self.dtype,
113
+ :default => self.stype == :dense ? 0 : self.default_value
114
+ }.merge(params[0])
115
+
116
+ self.cast_full(opts[:stype], opts[:dtype], opts[:default])
117
+ else
118
+ params << self.stype if params.size == 0
119
+ params << self.dtype if params.size == 1
120
+ params << (self.stype == :dense ? 0 : self.default_value) if params.size == 2
121
+
122
+ self.cast_full(*params)
123
+ end
124
+
125
+ end
126
+
85
127
  #
86
128
  # call-seq:
87
129
  # rows -> Integer
@@ -122,8 +164,10 @@ class NMatrix
122
164
  end
123
165
  end
124
166
  h
125
- else # dense and list should use a C internal functions.
126
- to_hash_c
167
+ else # dense and list should use a C internal function.
168
+ # FIXME: Write a C internal to_h function.
169
+ m = stype == :dense ? self.cast(:list, self.dtype) : self
170
+ m.__list_to_hash__
127
171
  end
128
172
  end
129
173
  alias :to_h :to_hash
@@ -293,6 +337,7 @@ class NMatrix
293
337
  '[' + ary.collect { |a| a ? a : 'nil'}.join(',') + ']'
294
338
  end
295
339
 
340
+
296
341
  ##
297
342
  # call-seq:
298
343
  # each_along_dim() -> Enumerator
@@ -317,6 +362,7 @@ class NMatrix
317
362
  end
318
363
  end
319
364
 
365
+
320
366
  ##
321
367
  # call-seq:
322
368
  # reduce_along_dim() -> Enumerator
@@ -441,7 +487,7 @@ class NMatrix
441
487
  def min(dimen=0)
442
488
  reduce_along_dim(dimen) do |min, sub_mat|
443
489
  if min.is_a? NMatrix then
444
- min * (min <= sub_mat) + ((min)*0.0 + (min > sub_mat)) * sub_mat
490
+ min * (min <= sub_mat).cast(self.stype, self.dtype) + ((min)*0.0 + (min > sub_mat).cast(self.stype, self.dtype)) * sub_mat
445
491
  else
446
492
  min <= sub_mat ? min : sub_mat
447
493
  end
@@ -460,7 +506,7 @@ class NMatrix
460
506
  def max(dimen=0)
461
507
  reduce_along_dim(dimen) do |max, sub_mat|
462
508
  if max.is_a? NMatrix then
463
- max * (max >= sub_mat) + ((max)*0.0 + (max < sub_mat)) * sub_mat
509
+ max * (max >= sub_mat).cast(self.stype, self.dtype) + ((max)*0.0 + (max < sub_mat).cast(self.stype, self.dtype)) * sub_mat
464
510
  else
465
511
  max >= sub_mat ? max : sub_mat
466
512
  end
@@ -508,7 +554,7 @@ class NMatrix
508
554
 
509
555
  ##
510
556
  # call-seq:
511
- # to_f() -> Float
557
+ # to_f -> Float
512
558
  #
513
559
  # Converts an nmatrix with a single element (but any number of dimensions)
514
560
  # to a float.
@@ -523,8 +569,24 @@ class NMatrix
523
569
 
524
570
  ##
525
571
  # call-seq:
526
- # map() -> Enumerator
527
- # map() { |elem| block } -> NMatrix
572
+ # each -> Enumerator
573
+ #
574
+ # Enumerate through the matrix. @see Enumerable#each
575
+ #
576
+ # For dense, this actually calls a specialized each iterator (in C). For yale and list, it relies upon
577
+ # #each_with_indices (which is about as fast as reasonably possible for C code).
578
+ def each &block
579
+ if self.stype == :dense
580
+ self.__dense_each__(&block)
581
+ else
582
+ self.each_with_indices(&block)
583
+ end
584
+ end
585
+
586
+ ##
587
+ # call-seq:
588
+ # map -> Enumerator
589
+ # map { |elem| block } -> NMatrix
528
590
  #
529
591
  # @see Enumerable#map
530
592
  #
@@ -537,8 +599,8 @@ class NMatrix
537
599
 
538
600
  ##
539
601
  # call-seq:
540
- # map!() -> Enumerator
541
- # map!() { |elem| block } -> NMatrix
602
+ # map! -> Enumerator
603
+ # map! { |elem| block } -> NMatrix
542
604
  #
543
605
  # Maps in place.
544
606
  # @see #map
@@ -620,7 +682,70 @@ class NMatrix
620
682
  end
621
683
  end
622
684
 
685
+
686
+ def method_missing name, *args, &block #:nodoc:
687
+ if name.to_s =~ /^__list_elementwise_.*__$/
688
+ raise NotImplementedError, "requested undefined list matrix element-wise operation"
689
+ elsif name.to_s =~ /^__yale_scalar_.*__$/
690
+ raise NotImplementedError, "requested undefined yale scalar element-wise operation"
691
+ else
692
+ super(name, *args, &block)
693
+ end
694
+ end
695
+
623
696
  protected
697
+ # Define the element-wise operations for lists. Note that the __list_map_merged_stored__ iterator returns a Ruby Object
698
+ # matrix, which we then cast back to the appropriate type. If you don't want that, you can redefine these functions in
699
+ # your own code.
700
+ {add: :+, sub: :-, mul: :*, div: :/, pow: :**, mod: :%}.each_pair do |ewop, op|
701
+ define_method("__list_elementwise_#{ewop}__") do |rhs|
702
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
703
+ end
704
+ define_method("__dense_elementwise_#{ewop}__") do |rhs|
705
+ self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
706
+ end
707
+ define_method("__yale_elementwise_#{ewop}__") do |rhs|
708
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
709
+ end
710
+ define_method("__list_scalar_#{ewop}__") do |rhs|
711
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
712
+ end
713
+ define_method("__yale_scalar_#{ewop}__") do |rhs|
714
+ self.__yale_map_stored__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
715
+ end
716
+ define_method("__dense_scalar_#{ewop}__") do |rhs|
717
+ self.__dense_map__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
718
+ end
719
+ end
720
+
721
+ # Equality operators do not involve a cast. We want to get back matrices of TrueClass and FalseClass.
722
+ {eqeq: :==, neq: :!=, lt: :<, gt: :>, leq: :<=, geq: :>=}.each_pair do |ewop, op|
723
+ define_method("__list_elementwise_#{ewop}__") do |rhs|
724
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
725
+ end
726
+ define_method("__dense_elementwise_#{ewop}__") do |rhs|
727
+ self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }
728
+ end
729
+ define_method("__yale_elementwise_#{ewop}__") do |rhs|
730
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
731
+ end
732
+
733
+ define_method("__list_scalar_#{ewop}__") do |rhs|
734
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
735
+ end
736
+ define_method("__yale_scalar_#{ewop}__") do |rhs|
737
+ self.__yale_map_stored__ { |l| l.send(op,rhs) }
738
+ end
739
+ define_method("__dense_scalar_#{ewop}__") do |rhs|
740
+ self.__dense_map__ { |l| l.send(op,rhs) }
741
+ end
742
+ end
743
+
744
+ # This is how you write an individual element-wise operation function:
745
+ #def __list_elementwise_add__ rhs
746
+ # self.__list_map_merged_stored__(rhs){ |l,r| l+r }.cast(self.stype, NMatrix.upcast(self.dtype, rhs.dtype))
747
+ #end
748
+
624
749
  def inspect_helper #:nodoc:
625
750
  ary = []
626
751
  ary << "shape:[#{shape.join(',')}]" << "dtype:#{dtype}" << "stype:#{stype}"
@@ -639,4 +764,6 @@ protected
639
764
 
640
765
  ary
641
766
  end
767
+
768
+
642
769
  end
@@ -248,6 +248,39 @@ class NVector < NMatrix
248
248
  t.shuffle!(*args)
249
249
  end
250
250
 
251
+ #
252
+ # call-seq:
253
+ # sorted_indices -> Array
254
+ #
255
+ # Returns an array of the indices ordered by value sorted.
256
+ #
257
+ def sorted_indices
258
+ ary = self.to_a
259
+ ary.each_index.sort_by { |i| ary[i] } # from: http://stackoverflow.com/a/17841159/170300
260
+ end
261
+
262
+ #
263
+ # call-seq:
264
+ # binned_sorted_indices -> Array
265
+ #
266
+ # Returns an array of arrays of indices ordered by value sorted. Functions basically like +sorted_indices+, but
267
+ # groups indices together for those values that are the same.
268
+ #
269
+ def binned_sorted_indices
270
+ ary = self.to_a
271
+ ary2 = []
272
+ last_bin = ary.each_index.sort_by { |i| [ary[i]] }.inject([]) do |result, element|
273
+ if result.empty? || ary[result[-1]] == ary[element]
274
+ result << element
275
+ else
276
+ ary2 << result
277
+ [element]
278
+ end
279
+ end
280
+ ary2 << last_bin unless last_bin.empty?
281
+ ary2
282
+ end
283
+
251
284
 
252
285
  # TODO: Make this actually pretty.
253
286
  def pretty_print(q = nil) #:nodoc:
@@ -133,7 +133,7 @@ class NMatrix
133
133
 
134
134
  # Fill the diagonal with 1's.
135
135
  m = NMatrix.zeros(stype, dim, dtype)
136
- (0 .. (dim - 1)).each do |i|
136
+ (0...dim).each do |i|
137
137
  m[i, i] = 1
138
138
  end
139
139
 
@@ -141,6 +141,49 @@ class NMatrix
141
141
  end
142
142
  alias :identity :eye
143
143
 
144
+ #
145
+ # call-seq:
146
+ # diagonals(array) -> NMatrix
147
+ # diagonals(stype, array, dtype) -> NMatrix
148
+ # diagonals(array, dtype) -> NMatrix
149
+ # diagonals(stype, array) -> NMatrix
150
+ #
151
+ # Creates a matrix filled with specified diagonals.
152
+ #
153
+ # * *Arguments* :
154
+ # - +stype+ -> (optional) Storage type for the matrix (default is :dense)
155
+ # - +entries+ -> Array containing input values for diagonal matrix
156
+ # - +dtype+ -> (optional) Default is based on values in supplied Array
157
+ # * *Returns* :
158
+ # - NMatrix filled with specified diagonal values.
159
+ #
160
+ # Examples:
161
+ #
162
+ # NMatrix.diagonal([1.0,2,3,4]) # => 1.0 0.0 0.0 0.0
163
+ # 0.0 2.0 0.0 0.0
164
+ # 0.0 0.0 3.0 0.0
165
+ # 0.0 0.0 0.0 4.0
166
+ #
167
+ # NMatrix.diagonal(:dense, [1,2,3,4], :int32) # => 1 0 0 0
168
+ # 0 2 0 0
169
+ # 0 0 3 0
170
+ # 0 0 0 4
171
+ #
172
+ #
173
+ def diagonal(*params)
174
+ dtype = params.last.is_a?(Symbol) ? params.pop : nil
175
+ stype = params.first.is_a?(Symbol) ? params.shift : :dense
176
+ ary = params.shift
177
+
178
+ m = NMatrix.zeros(stype, ary.length, dtype || guess_dtype(ary[0]))
179
+ ary.each_with_index do |n, i|
180
+ m[i,i] = n
181
+ end
182
+ m
183
+ end
184
+ alias :diag :diagonal
185
+ alias :diagonals :diagonal
186
+
144
187
  #
145
188
  # call-seq:
146
189
  # random(size) -> NMatrix
@@ -527,33 +570,29 @@ class NVector < NMatrix
527
570
  # Returns a NVector with +n+ values of dtype +:float64+ equally spaced from
528
571
  # +a+ to +b+, inclusive.
529
572
  #
530
- # Following the MATLAB implementation, if n isn't provided it's assumed to
531
- # be 100.
573
+ # See: http://www.mathworks.com/help/matlab/ref/linspace.html
532
574
  #
533
575
  # * *Arguments* :
534
576
  # - +a+ -> The first value in the sequence.
535
577
  # - +b+ -> The last value in the sequence.
536
- # - +n+ -> The number of elements.
578
+ # - +n+ -> The number of elements. Default is 100.
537
579
  # * *Returns* :
538
- # - n-by-1 NMatrix with +n+ +:float64+ values.
580
+ # - NVector with +n+ +:float64+ values.
539
581
  #
540
582
  # Example:
541
583
  # x = NVector.linspace(0, Math::PI, 1000)
542
- # => #<NMatrix:0x007f83921992f0shape:[1000,1] dtype:float64 stype:dense>
543
- #
544
- # x.pp
545
- # [0.0]
546
- # [0.0031447373909807737]
547
- # [0.006289474781961547]
584
+ # x.pretty_print
585
+ # [0.0
586
+ # 0.0031447373909807737
587
+ # 0.006289474781961547
548
588
  # ...
549
- # [3.135303178807831]
550
- # [3.138447916198812]
551
- # [3.141592653589793]
589
+ # 3.135303178807831
590
+ # 3.138447916198812
591
+ # 3.141592653589793]
552
592
  # => nil
553
593
  #
554
594
  def linspace(a, b, n = 100)
555
- # See: http://www.mathworks.com/help/matlab/ref/linspace.html
556
- # Formula: seq(n) * step + a
595
+ # Formula: seq(n) * step + a
557
596
 
558
597
  # step = ((b - a) / (n - 1))
559
598
  step = (b - a) * (1.0 / (n - 1))
@@ -563,6 +602,46 @@ class NVector < NMatrix
563
602
  result += NVector.new(n, a, :float64)
564
603
  result
565
604
  end
605
+
606
+ #
607
+ # call-seq:
608
+ # logspace(a, b) -> NVector
609
+ # logspace(a, b, n) -> NVector
610
+ #
611
+ # Returns a NVector with +n+ values of dtype +:float64+ logarithmically
612
+ # spaced from +10^a+ to +10^b+, inclusive.
613
+ #
614
+ # See: http://www.mathworks.com/help/matlab/ref/logspace.html
615
+ #
616
+ # * *Arguments* :
617
+ # - +a+ -> The first value in the sequence.
618
+ # - +b+ -> The last value in the sequence.
619
+ # - +n+ -> The number of elements. Default is 100.
620
+ # * *Returns* :
621
+ # - NVector with +n+ +:float64+ values.
622
+ #
623
+ # Example:
624
+ # x = NVector.logspace(0, Math::PI, 10)
625
+ # x.pretty_print
626
+ # [1.0
627
+ # 2.2339109164570266
628
+ # 4.990357982665873
629
+ # 11.148015174505757
630
+ # 24.903672795156997
631
+ # 55.632586516975095
632
+ # 124.27824233101062
633
+ # 277.6265222213364
634
+ # 620.1929186882427
635
+ # 1385.4557313670107]
636
+ # => nil
637
+ #
638
+ def logspace(a, b, n = 100)
639
+ # Formula: 10^a, 10^(a + step), ..., 10^b, where step = ((b-a) / (n-1)).
640
+
641
+ result = NVector.linspace(a, b, n)
642
+ result.each_stored_with_index { |element, i| result[i] = 10 ** element }
643
+ result
644
+ end
566
645
  end
567
646
  end
568
647