nmatrix 0.0.5 → 0.0.6

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 (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