nmatrix 0.0.6 → 0.0.7

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Gemfile +5 -0
  4. data/History.txt +97 -0
  5. data/Manifest.txt +34 -7
  6. data/README.rdoc +13 -13
  7. data/Rakefile +36 -26
  8. data/ext/nmatrix/data/data.cpp +15 -2
  9. data/ext/nmatrix/data/data.h +4 -0
  10. data/ext/nmatrix/data/ruby_object.h +5 -14
  11. data/ext/nmatrix/extconf.rb +3 -2
  12. data/ext/nmatrix/{util/math.cpp → math.cpp} +296 -6
  13. data/ext/nmatrix/math/asum.h +143 -0
  14. data/ext/nmatrix/math/geev.h +82 -0
  15. data/ext/nmatrix/math/gemm.h +267 -0
  16. data/ext/nmatrix/math/gemv.h +208 -0
  17. data/ext/nmatrix/math/ger.h +96 -0
  18. data/ext/nmatrix/math/gesdd.h +80 -0
  19. data/ext/nmatrix/math/gesvd.h +78 -0
  20. data/ext/nmatrix/math/getf2.h +86 -0
  21. data/ext/nmatrix/math/getrf.h +240 -0
  22. data/ext/nmatrix/math/getri.h +107 -0
  23. data/ext/nmatrix/math/getrs.h +125 -0
  24. data/ext/nmatrix/math/idamax.h +86 -0
  25. data/ext/nmatrix/{util → math}/lapack.h +60 -356
  26. data/ext/nmatrix/math/laswp.h +165 -0
  27. data/ext/nmatrix/math/long_dtype.h +52 -0
  28. data/ext/nmatrix/math/math.h +1154 -0
  29. data/ext/nmatrix/math/nrm2.h +181 -0
  30. data/ext/nmatrix/math/potrs.h +125 -0
  31. data/ext/nmatrix/math/rot.h +141 -0
  32. data/ext/nmatrix/math/rotg.h +115 -0
  33. data/ext/nmatrix/math/scal.h +73 -0
  34. data/ext/nmatrix/math/swap.h +73 -0
  35. data/ext/nmatrix/math/trsm.h +383 -0
  36. data/ext/nmatrix/nmatrix.cpp +176 -152
  37. data/ext/nmatrix/nmatrix.h +1 -2
  38. data/ext/nmatrix/ruby_constants.cpp +9 -4
  39. data/ext/nmatrix/ruby_constants.h +1 -0
  40. data/ext/nmatrix/storage/dense.cpp +57 -41
  41. data/ext/nmatrix/storage/list.cpp +52 -50
  42. data/ext/nmatrix/storage/storage.cpp +59 -43
  43. data/ext/nmatrix/storage/yale.cpp +352 -333
  44. data/ext/nmatrix/storage/yale.h +4 -0
  45. data/lib/nmatrix.rb +2 -2
  46. data/lib/nmatrix/blas.rb +4 -4
  47. data/lib/nmatrix/enumerate.rb +241 -0
  48. data/lib/nmatrix/lapack.rb +54 -1
  49. data/lib/nmatrix/math.rb +462 -0
  50. data/lib/nmatrix/nmatrix.rb +210 -486
  51. data/lib/nmatrix/nvector.rb +0 -62
  52. data/lib/nmatrix/rspec.rb +75 -0
  53. data/lib/nmatrix/shortcuts.rb +136 -108
  54. data/lib/nmatrix/version.rb +1 -1
  55. data/spec/blas_spec.rb +20 -12
  56. data/spec/elementwise_spec.rb +22 -13
  57. data/spec/io_spec.rb +1 -0
  58. data/spec/lapack_spec.rb +197 -0
  59. data/spec/nmatrix_spec.rb +39 -38
  60. data/spec/nvector_spec.rb +3 -9
  61. data/spec/rspec_monkeys.rb +29 -0
  62. data/spec/rspec_spec.rb +34 -0
  63. data/spec/shortcuts_spec.rb +14 -16
  64. data/spec/slice_spec.rb +242 -186
  65. data/spec/spec_helper.rb +19 -0
  66. metadata +33 -5
  67. data/ext/nmatrix/util/math.h +0 -2612
@@ -90,6 +90,7 @@ extern "C" {
90
90
  YALE_STORAGE* nm_yale_storage_create_from_old_yale(nm::dtype_t dtype, size_t* shape, void* ia, void* ja, void* a, nm::dtype_t from_dtype);
91
91
  YALE_STORAGE* nm_yale_storage_create_merged(const YALE_STORAGE* merge_template, const YALE_STORAGE* other);
92
92
  void nm_yale_storage_delete(STORAGE* s);
93
+ void nm_yale_storage_delete_ref(STORAGE* s);
93
94
  void nm_yale_storage_init(YALE_STORAGE* s, void* default_val);
94
95
  void nm_yale_storage_mark(void*);
95
96
 
@@ -221,6 +222,9 @@ namespace nm { namespace yale_storage {
221
222
 
222
223
  template <typename IType>
223
224
  size_t get_size(const YALE_STORAGE* storage);
225
+
226
+ template <typename IType>
227
+ IType binary_search_left_boundary(const YALE_STORAGE* s, IType left, IType right, IType bound);
224
228
  }} // end of namespace nm::yale_storage
225
229
 
226
230
  #endif // YALE_H
data/lib/nmatrix.rb CHANGED
@@ -26,7 +26,7 @@
26
26
  #
27
27
 
28
28
  # For some reason nmatrix.so ends up in a different place during gem build.
29
- if File.exist? "lib/nmatrix/nmatrix.so"
29
+ if File.exist?("lib/nmatrix/nmatrix.so") #|| File.exist?("lib/nmatrix/nmatrix.bundle")
30
30
  # Development
31
31
  require "nmatrix/nmatrix.so"
32
32
  else
@@ -36,7 +36,7 @@ end
36
36
 
37
37
  require 'nmatrix/nmatrix.rb'
38
38
  require 'nmatrix/version.rb'
39
- require 'nmatrix/nvector.rb'
39
+ #require 'nmatrix/nvector.rb'
40
40
  require 'nmatrix/blas.rb'
41
41
  require 'nmatrix/monkeys'
42
42
  require "nmatrix/shortcuts.rb"
data/lib/nmatrix/blas.rb CHANGED
@@ -180,7 +180,7 @@ module NMatrix::BLAS
180
180
  # - +ArgumentError+ -> Need to supply n for non-standard incx, incy values.
181
181
  #
182
182
  def rot(x, y, c, s, incx = 1, incy = 1, n = nil, in_place=false)
183
- raise(ArgumentError, 'Expected dense NVectors as first two arguments.') unless x.is_a?(NMatrix) and y.is_a?(NMatrix) and x.stype == :dense and y.stype == :dense
183
+ raise(ArgumentError, 'Expected dense NMatrices as first two arguments.') unless x.is_a?(NMatrix) and y.is_a?(NMatrix) and x.stype == :dense and y.stype == :dense
184
184
  raise(ArgumentError, 'NMatrix dtype mismatch.') unless x.dtype == y.dtype
185
185
  raise(ArgumentError, 'Need to supply n for non-standard incx, incy values') if n.nil? && incx != 1 && incx != -1 && incy != 1 && incy != -1
186
186
 
@@ -228,7 +228,7 @@ module NMatrix::BLAS
228
228
  # - +ArgumentError+ -> Expected dense NVector of size 2
229
229
  #
230
230
  def rotg(ab)
231
- raise(ArgumentError, "Expected dense NVector of size 2") unless ab.is_a?(NVector) && ab.size == 2
231
+ raise(ArgumentError, "Expected dense NMatrix of shape [2,1] or [1,2]") unless ab.is_a?(NMatrix) && ab.stype == :dense && ab.size == 2
232
232
 
233
233
  ::NMatrix::BLAS.cblas_rotg(ab)
234
234
  end
@@ -252,7 +252,7 @@ module NMatrix::BLAS
252
252
  #
253
253
  def asum(x, incx = 1, n = nil)
254
254
  n ||= x.size / incx
255
- raise(ArgumentError, "Expected dense NVector (or NMatrix on rare occasions) for arg 0") unless x.is_a?(NMatrix)
255
+ raise(ArgumentError, "Expected dense NMatrix for arg 0") unless x.is_a?(NMatrix)
256
256
  raise(RangeError, "n out of range") if n*incx > x.size || n*incx <= 0 || n <= 0
257
257
  ::NMatrix::BLAS.cblas_asum(n, x, incx)
258
258
  end
@@ -275,7 +275,7 @@ module NMatrix::BLAS
275
275
  #
276
276
  def nrm2(x, incx = 1, n = nil)
277
277
  n ||= x.size / incx
278
- raise(ArgumentError, "Expected dense NVector (or NMatrix on rare occasions) for arg 0") unless x.is_a?(NMatrix)
278
+ raise(ArgumentError, "Expected dense NMatrix for arg 0") unless x.is_a?(NMatrix)
279
279
  raise(RangeError, "n out of range") if n*incx > x.size || n*incx <= 0 || n <= 0
280
280
  ::NMatrix::BLAS.cblas_nrm2(n, x, incx)
281
281
  end
@@ -0,0 +1,241 @@
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 - 2013, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2013, 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
+ # == enumerate.rb
25
+ #
26
+ # Enumeration methods for NMatrix
27
+ #++
28
+
29
+ class NMatrix
30
+ include Enumerable
31
+
32
+ ##
33
+ # call-seq:
34
+ # each -> Enumerator
35
+ #
36
+ # Enumerate through the matrix. @see Enumerable#each
37
+ #
38
+ # For dense, this actually calls a specialized each iterator (in C). For yale and list, it relies upon
39
+ # #each_with_indices (which is about as fast as reasonably possible for C code).
40
+ def each &bl
41
+ if self.stype == :dense
42
+ self.__dense_each__(&bl)
43
+ elsif block_given?
44
+ self.each_with_indices(&bl)
45
+ else # Handle case where no block is given
46
+ Enumerator.new do |yielder|
47
+ self.each_with_indices do |params|
48
+ yielder.yield params
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ #
55
+ # call-seq:
56
+ # flat_map -> Enumerator
57
+ # flat_map { |elem| block } -> Array
58
+ #
59
+ # Maps using Enumerator (returns an Array or an Enumerator)
60
+ alias_method :flat_map, :map
61
+
62
+ ##
63
+ # call-seq:
64
+ # map -> Enumerator
65
+ # map { |elem| block } -> NMatrix
66
+ #
67
+ # Returns an NMatrix if a block is given. For an Array, use #flat_map
68
+ #
69
+ def map(&bl)
70
+ return enum_for(:map) unless block_given?
71
+ cp = self.dup
72
+ cp.map! &bl
73
+ cp
74
+ end
75
+
76
+ ##
77
+ # call-seq:
78
+ # map! -> Enumerator
79
+ # map! { |elem| block } -> NMatrix
80
+ #
81
+ # Maps in place.
82
+ # @see #map
83
+ #
84
+ def map!
85
+ return enum_for(:map!) unless block_given?
86
+ self.each_stored_with_indices do |e, *i|
87
+ self[*i] = (yield e)
88
+ end
89
+ self
90
+ end
91
+
92
+
93
+ #
94
+ # call-seq:
95
+ # each_rank() -> NMatrix
96
+ # each_rank() { |rank| block } -> NMatrix
97
+ # each_rank(dimen) -> Enumerator
98
+ # each_rank(dimen) { |rank| block } -> NMatrix
99
+ #
100
+ # Generic for @each_row, @each_col
101
+ #
102
+ # Iterate through each rank by reference.
103
+ #
104
+ # @param [Fixnum] dimen the rank being iterated over.
105
+ #
106
+ def each_rank(dimen=0, get_by=:reference)
107
+ return enum_for(:each_rank, dimen, get_by) unless block_given?
108
+ (0...self.shape[dimen]).each do |idx|
109
+ yield self.rank(dimen, idx, get_by)
110
+ end
111
+ self
112
+ end
113
+ alias :each_along_dim :each_rank
114
+
115
+ #
116
+ # call-seq:
117
+ # each_row { |row| block } -> NMatrix
118
+ #
119
+ # Iterate through each row, referencing it as an NMatrix slice.
120
+ def each_row(get_by=:reference)
121
+ return enum_for(:each_row, get_by) unless block_given?
122
+ (0...self.shape[0]).each do |i|
123
+ yield self.row(i, get_by)
124
+ end
125
+ self
126
+ end
127
+
128
+ #
129
+ # call-seq:
130
+ # each_column { |column| block } -> NMatrix
131
+ #
132
+ # Iterate through each column, referencing it as an NMatrix slice.
133
+ def each_column(get_by=:reference)
134
+ return enum_for(:each_column, get_by) unless block_given?
135
+ (0...self.shape[1]).each do |j|
136
+ yield self.column(j, get_by)
137
+ end
138
+ self
139
+ end
140
+
141
+ #
142
+ # call-seq:
143
+ # each_layer -> { |column| block } -> ...
144
+ #
145
+ # Iterate through each layer, referencing it as an NMatrix slice.
146
+ #
147
+ # Note: If you have a 3-dimensional matrix, the first dimension contains rows,
148
+ # the second contains columns, and the third contains layers.
149
+ def each_layer(get_by=:reference)
150
+ return enum_for(:each_layer, get_by) unless block_given?
151
+ (0...self.shape[2]).each do |k|
152
+ yield self.layer(k, get_by)
153
+ end
154
+ self
155
+ end
156
+
157
+
158
+ #
159
+ # call-seq:
160
+ # each_stored_with_index -> Enumerator
161
+ #
162
+ # Allow iteration across a vector NMatrix's stored values. See also @each_stored_with_indices
163
+ #
164
+ def each_stored_with_index(&block)
165
+ raise(NotImplementedError, "only works for dim 2 vectors") unless self.dim <= 2
166
+ return enum_for(:each_stored_with_index) unless block_given?
167
+
168
+ self.each_stored_with_indices do |v, i, j|
169
+ if shape[0] == 1
170
+ yield(v,j)
171
+ elsif shape[1] == 1
172
+ yield(v,i)
173
+ else
174
+ method_missing(:each_stored_with_index, &block)
175
+ end
176
+ end
177
+ self
178
+ end
179
+
180
+
181
+ ##
182
+ # call-seq:
183
+ # inject_rank() -> Enumerator
184
+ # inject_rank(dimen) -> Enumerator
185
+ # inject_rank(dimen, initial) -> Enumerator
186
+ # inject_rank(dimen, initial, dtype) -> Enumerator
187
+ # inject_rank() { |elem| block } -> NMatrix
188
+ # inject_rank(dimen) { |elem| block } -> NMatrix
189
+ # inject_rank(dimen, initial) { |elem| block } -> NMatrix
190
+ # inject_rank(dimen, initial, dtype) { |elem| block } -> NMatrix
191
+ #
192
+ # Reduces an NMatrix using a supplied block over a specified dimension.
193
+ # The block should behave the same way as for Enumerable#reduce.
194
+ #
195
+ # @param [Integer] dimen the dimension being reduced
196
+ # @param [Numeric] initial the initial value for the reduction
197
+ # (i.e. the usual parameter to Enumerable#reduce). Supply nil or do not
198
+ # supply this argument to have it follow the usual Enumerable#reduce
199
+ # behavior of using the first element as the initial value.
200
+ # @param [Symbol] dtype if non-nil/false, forces the accumulated result to have this dtype
201
+ # @return [NMatrix] an NMatrix with the same number of dimensions as the
202
+ # input, but with the input dimension now having size 1. Each element
203
+ # is the result of the reduction at that position along the specified
204
+ # dimension.
205
+ #
206
+ def inject_rank(dimen=0, initial=nil, dtype=nil)
207
+
208
+ raise(RangeError, "requested dimension (#{dimen}) does not exist (shape: #{shape})") if dimen > self.dim
209
+
210
+ return enum_for(:inject_rank, dimen, initial, dtype) unless block_given?
211
+
212
+ new_shape = shape
213
+ new_shape[dimen] = 1
214
+
215
+ first_as_acc = false
216
+
217
+ if initial then
218
+ acc = NMatrix.new(new_shape, initial, dtype || self.dtype)
219
+ else
220
+ each_rank(dimen) do |sub_mat|
221
+ acc = (sub_mat.is_a?(NMatrix) and !dtype.nil? and dtype != self.dtype) ? sub_mat.cast(self.stype, dtype) : sub_mat
222
+ break
223
+ end
224
+ first_as_acc = true
225
+ end
226
+
227
+ each_rank(dimen) do |sub_mat|
228
+ if first_as_acc
229
+ first_as_acc = false
230
+ next
231
+ end
232
+ acc = yield(acc, sub_mat)
233
+ end
234
+
235
+ acc
236
+ end
237
+
238
+ alias :reduce_along_dim :inject_rank
239
+ alias :inject_along_dim :inject_rank
240
+
241
+ end
@@ -120,6 +120,59 @@ class NMatrix
120
120
  clapack_potrs(order, uplo, n, nrhs, a, lda, b, ldb)
121
121
  end
122
122
 
123
+ #
124
+ # call-seq:
125
+ # gesvd(matrix, type)
126
+ #
127
+ #
128
+ # * *Arguments* :
129
+ # - +matrix+ -> matrix for which to compute the singular values ##TODO make this a self
130
+ # - +type+ -> :all_values, :both, :left, :right, :left_matrix, :right_matrix, :overwrite_right, :overwrite_left, :none , or signifying what combination of singular values and matrices are desired in your output.
131
+ # * *Returns* :
132
+ # - Array with the result values in an array
133
+ # * *Raises* :
134
+ # - +ArgumentError+ -> Expected dense NMatrix as first argument.
135
+ #
136
+ def gesvd(matrix, type = :both)
137
+ raise ArgumentError, 'Expected dense NMatrix as first argument.' unless matrix.is_a?(NMatrix) and matrix.stype == :dense
138
+ #define jobu, jobvt
139
+ jobu, jobvt = :none, :none
140
+ case type
141
+ when :both
142
+ jobu, jobvt = :all, :all
143
+ when :arrays
144
+ jobu, jobvt = :return, :return
145
+ when :left
146
+ jobu = :return
147
+ when :right
148
+ jobvt = :return
149
+ end
150
+
151
+ # Build up the u and vt matrices
152
+ m, n = matrix.shape
153
+ dtype = matrix.dtype
154
+ s_matrix = NMatrix.new([1,matrix.shape.min], dtype)
155
+ u_matrix = NMatrix.new([m,m], dtype)
156
+ v_matrix = NMatrix.new([n,n], dtype)
157
+ # test this
158
+ s = gesvd(type, matrix, s_matrix, u_matrix, v_matrix)
159
+
160
+ # what should this return?
161
+ [s_matrix, u_matrix, v_matrix]
162
+ end # #svd
163
+
164
+ # laswp(matrix, ipiv) -> NMatrix
165
+ #
166
+ # Permute the columns of a matrix (in-place) according to the Array +ipiv+.
167
+ #
168
+ def laswp(matrix, ipiv)
169
+ raise(ArgumentError, "expected NMatrix for argument 0") unless matrix.is_a?(NMatrix)
170
+ raise(StorageTypeError, "LAPACK functions only work on :dense NMatrix instances") unless matrix.stype == :dense
171
+ raise(ArgumentError, "expected Array ipiv to have no more entries than NMatrix a has columns") if ipiv.size > matrix.shape[1]
172
+
173
+ clapack_laswp(matrix.shape[0], matrix, matrix.shape[1], 0, ipiv.size-1, ipiv, 1)
174
+ end
175
+
123
176
  end
124
177
  end
125
- end
178
+ end
@@ -0,0 +1,462 @@
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 - 2013, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2013, 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
+ # == math.rb
25
+ #
26
+ # Math functionality for NMatrix, along with any NMatrix instance
27
+ # methods that correspond to ATLAS/BLAS/LAPACK functions (e.g.,
28
+ # laswp).
29
+ #++
30
+
31
+ class NMatrix
32
+ #
33
+ # call-seq:
34
+ # invert! -> NMatrix
35
+ #
36
+ # Use LAPACK to calculate the inverse of the matrix (in-place). Only works on
37
+ # dense matrices.
38
+ #
39
+ # Note: If you don't have LAPACK, e.g., on a Mac, this may not work yet.
40
+ #
41
+ def invert!
42
+ # Get the pivot array; factor the matrix
43
+ pivot = self.getrf!
44
+
45
+ # Now calculate the inverse using the pivot array
46
+ NMatrix::LAPACK::clapack_getri(:row, self.shape[1], self, self.shape[1], pivot)
47
+
48
+ self
49
+ end
50
+
51
+ #
52
+ # call-seq:
53
+ # invert -> NMatrix
54
+ #
55
+ # Make a copy of the matrix, then invert it (requires LAPACK).
56
+ #
57
+ # * *Returns* :
58
+ # - A dense NMatrix.
59
+ #
60
+ def invert
61
+ self.cast(:dense, self.dtype).invert!
62
+ end
63
+ alias :inverse :invert
64
+
65
+ #
66
+ # call-seq:
67
+ # getrf! -> NMatrix
68
+ #
69
+ # LU factorization of a general M-by-N matrix +A+ using partial pivoting with
70
+ # row interchanges. Only works in dense matrices.
71
+ #
72
+ # * *Returns* :
73
+ # - The IPIV vector. The L and U matrices are stored in A.
74
+ # * *Raises* :
75
+ # - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
76
+ #
77
+ def getrf!
78
+ raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.stype == :dense
79
+ NMatrix::LAPACK::clapack_getrf(:row, self.shape[0], self.shape[1], self, self.shape[1])
80
+ end
81
+
82
+ #
83
+ # call-seq:
84
+ # factorize_lu -> ...
85
+ #
86
+ # LU factorization of a matrix.
87
+ #
88
+ # FIXME: For some reason, getrf seems to require that the matrix be transposed first -- and then you have to transpose the
89
+ # FIXME: result again. Ideally, this would be an in-place factorize instead, and would be called nm_factorize_lu_bang.
90
+ #
91
+ def factorize_lu
92
+ raise(NotImplementedError, "only implemented for dense storage") unless self.stype == :dense
93
+ raise(NotImplementedError, "matrix is not 2-dimensional") unless self.dimensions == 2
94
+
95
+ t = self.transpose
96
+ NMatrix::LAPACK::clapack_getrf(:row, t.shape[0], t.shape[1], t, t.shape[1])
97
+ t.transpose
98
+ end
99
+
100
+ def alloc_svd_result
101
+ [
102
+ NMatrix.new(:dense, self.shape[0], self.dtype),
103
+ NMatrix.new(:dense, [self.shape[0],1], self.dtype),
104
+ NMatrix.new(:dense, self.shape[1], self.dtype)
105
+ ]
106
+ end
107
+
108
+ #
109
+ # call-seq:
110
+ # gesvd -> [u, sigma, v_transpose]
111
+ # gesvd -> [u, sigma, v_conjugate_transpose] # complex
112
+ #
113
+ # Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
114
+ #
115
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
116
+ # requires.
117
+ #
118
+ def gesvd(workspace_size=1)
119
+ result = alloc_svd_result
120
+ NMatrix::LAPACK::lapack_gesvd(:a, :a, self.shape[0], self.shape[1], self, self.shape[0], result[1], result[0], self.shape[0], result[2], self.shape[1], workspace_size)
121
+ result
122
+ end
123
+
124
+
125
+ #
126
+ # call-seq:
127
+ # gesdd -> [u, sigma, v_transpose]
128
+ # gesdd -> [u, sigma, v_conjugate_transpose] # complex
129
+ #
130
+ # Compute the singular value decomposition of a matrix using LAPACK's GESDD function. This uses a divide-and-conquer
131
+ # strategy. See also #gesvd.
132
+ #
133
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
134
+ # requires.
135
+ #
136
+ def gesdd(workspace_size=1)
137
+ result = alloc_svd_result
138
+ NMatrix::LAPACK::lapack_gesvd(:a, :a, self.shape[0], self.shape[1], self, self.shape[0], result[1], result[0], self.shape[0], result[2], self.shape[1], workspace_size)
139
+ result
140
+ end
141
+
142
+ #
143
+ # call-seq:
144
+ # laswp!(ary) -> NMatrix
145
+ #
146
+ # In-place permute the columns of a dense matrix using LASWP according to the order given in an Array +ary+.
147
+ # Not yet implemented for yale or list.
148
+ def laswp!(ary)
149
+ NMatrix::LAPACK::laswp(self, ary)
150
+ end
151
+
152
+ #
153
+ # call-seq:
154
+ # laswp(ary) -> NMatrix
155
+ #
156
+ # Permute the columns of a dense matrix using LASWP according to the order given in an Array +ary+.
157
+ # Not yet implemented for yale or list.
158
+ def laswp(ary)
159
+ self.clone.laswp!(ary)
160
+ end
161
+
162
+ #
163
+ # call-seq:
164
+ # det -> determinant
165
+ #
166
+ # Calculate the determinant by way of LU decomposition. This is accomplished
167
+ # using clapack_getrf, and then by summing the diagonal elements. There is a
168
+ # risk of underflow/overflow.
169
+ #
170
+ # There are probably also more efficient ways to calculate the determinant.
171
+ # This method requires making a copy of the matrix, since clapack_getrf
172
+ # modifies its input.
173
+ #
174
+ # For smaller matrices, you may be able to use +#det_exact+.
175
+ #
176
+ # This function is guaranteed to return the same type of data in the matrix
177
+ # upon which it is called.
178
+ # In other words, if you call it on a rational matrix, you'll get a rational
179
+ # number back.
180
+ #
181
+ # Integer matrices are converted to rational matrices for the purposes of
182
+ # performing the calculation, as xGETRF can't work on integer matrices.
183
+ #
184
+ # * *Returns* :
185
+ # - The determinant of the matrix. It's the same type as the matrix's dtype.
186
+ # * *Raises* :
187
+ # - +NotImplementedError+ -> Must be used in 2D matrices.
188
+ #
189
+ def det
190
+ raise(NotImplementedError, "determinant can be calculated only for 2D matrices") unless self.dim == 2
191
+
192
+ # Cast to a dtype for which getrf is implemented
193
+ new_dtype = [:byte,:int8,:int16,:int32,:int64].include?(self.dtype) ? :rational128 : self.dtype
194
+ copy = self.cast(:dense, new_dtype)
195
+
196
+ # Need to know the number of permutations. We'll add up the diagonals of
197
+ # the factorized matrix.
198
+ pivot = copy.getrf!
199
+
200
+ prod = pivot.size % 2 == 1 ? -1 : 1 # odd permutations => negative
201
+ [shape[0],shape[1]].min.times do |i|
202
+ prod *= copy[i,i]
203
+ end
204
+
205
+ # Convert back to an integer if necessary
206
+ new_dtype != self.dtype ? prod.to_i : prod
207
+ end
208
+
209
+ #
210
+ # call-seq:
211
+ # complex_conjugate -> NMatrix
212
+ # complex_conjugate(new_stype) -> NMatrix
213
+ #
214
+ # Get the complex conjugate of this matrix. See also complex_conjugate! for
215
+ # an in-place operation (provided the dtype is already +:complex64+ or
216
+ # +:complex128+).
217
+ #
218
+ # Doesn't work on list matrices, but you can optionally pass in the stype you
219
+ # want to cast to if you're dealing with a list matrix.
220
+ #
221
+ # * *Arguments* :
222
+ # - +new_stype+ -> stype for the new matrix.
223
+ # * *Returns* :
224
+ # - If the original NMatrix isn't complex, the result is a +:complex128+ NMatrix. Otherwise, it's the original dtype.
225
+ #
226
+ def complex_conjugate(new_stype = self.stype)
227
+ self.cast(new_stype, NMatrix::upcast(dtype, :complex64)).complex_conjugate!
228
+ end
229
+
230
+ #
231
+ # call-seq:
232
+ # conjugate_transpose -> NMatrix
233
+ #
234
+ # Calculate the conjugate transpose of a matrix. If your dtype is already
235
+ # complex, this should only require one copy (for the transpose).
236
+ #
237
+ # * *Returns* :
238
+ # - The conjugate transpose of the matrix as a copy.
239
+ #
240
+ def conjugate_transpose
241
+ self.transpose.complex_conjugate!
242
+ end
243
+
244
+ #
245
+ # call-seq:
246
+ # hermitian? -> Boolean
247
+ #
248
+ # A hermitian matrix is a complex square matrix that is equal to its
249
+ # conjugate transpose. (http://en.wikipedia.org/wiki/Hermitian_matrix)
250
+ #
251
+ # * *Returns* :
252
+ # - True if +self+ is a hermitian matrix, nil otherwise.
253
+ #
254
+ def hermitian?
255
+ return false if self.dim != 2 or self.shape[0] != self.shape[1]
256
+
257
+ if [:complex64, :complex128].include?(self.dtype)
258
+ # TODO: Write much faster Hermitian test in C
259
+ self.eql?(self.conjugate_transpose)
260
+ else
261
+ symmetric?
262
+ end
263
+ end
264
+
265
+ ##
266
+ # call-seq:
267
+ # mean() -> NMatrix
268
+ # mean(dimen) -> NMatrix
269
+ #
270
+ # Calculates the mean along the specified dimension.
271
+ #
272
+ # This will force integer types to float64 dtype.
273
+ #
274
+ # @see #inject_rank
275
+ #
276
+ def mean(dimen=0)
277
+ reduce_dtype = nil
278
+ if integer_dtype? then
279
+ reduce_dtype = :float64
280
+ end
281
+ inject_rank(dimen, 0.0, reduce_dtype) do |mean, sub_mat|
282
+ mean + sub_mat/shape[dimen]
283
+ end
284
+ end
285
+
286
+ ##
287
+ # call-seq:
288
+ # sum() -> NMatrix
289
+ # sum(dimen) -> NMatrix
290
+ #
291
+ # Calculates the sum along the specified dimension.
292
+ #
293
+ # @see #inject_rank
294
+ def sum(dimen=0)
295
+ inject_rank(dimen, 0.0) do |sum, sub_mat|
296
+ sum + sub_mat
297
+ end
298
+ end
299
+
300
+
301
+ ##
302
+ # call-seq:
303
+ # min() -> NMatrix
304
+ # min(dimen) -> NMatrix
305
+ #
306
+ # Calculates the minimum along the specified dimension.
307
+ #
308
+ # @see #inject_rank
309
+ #
310
+ def min(dimen=0)
311
+ inject_rank(dimen) do |min, sub_mat|
312
+ if min.is_a? NMatrix then
313
+ min * (min <= sub_mat).cast(self.stype, self.dtype) + ((min)*0.0 + (min > sub_mat).cast(self.stype, self.dtype)) * sub_mat
314
+ else
315
+ min <= sub_mat ? min : sub_mat
316
+ end
317
+ end
318
+ end
319
+
320
+ ##
321
+ # call-seq:
322
+ # max() -> NMatrix
323
+ # max(dimen) -> NMatrix
324
+ #
325
+ # Calculates the maximum along the specified dimension.
326
+ #
327
+ # @see #inject_rank
328
+ #
329
+ def max(dimen=0)
330
+ inject_rank(dimen) do |max, sub_mat|
331
+ if max.is_a? NMatrix then
332
+ max * (max >= sub_mat).cast(self.stype, self.dtype) + ((max)*0.0 + (max < sub_mat).cast(self.stype, self.dtype)) * sub_mat
333
+ else
334
+ max >= sub_mat ? max : sub_mat
335
+ end
336
+ end
337
+ end
338
+
339
+
340
+ ##
341
+ # call-seq:
342
+ # variance() -> NMatrix
343
+ # variance(dimen) -> NMatrix
344
+ #
345
+ # Calculates the sample variance along the specified dimension.
346
+ #
347
+ # This will force integer types to float64 dtype.
348
+ #
349
+ # @see #inject_rank
350
+ #
351
+ def variance(dimen=0)
352
+ reduce_dtype = nil
353
+ if integer_dtype? then
354
+ reduce_dtype = :float64
355
+ end
356
+ m = mean(dimen)
357
+ inject_rank(dimen, 0.0, reduce_dtype) do |var, sub_mat|
358
+ var + (m - sub_mat)*(m - sub_mat)/(shape[dimen]-1)
359
+ end
360
+ end
361
+
362
+ ##
363
+ # call-seq:
364
+ # std() -> NMatrix
365
+ # std(dimen) -> NMatrix
366
+ #
367
+ #
368
+ # Calculates the sample standard deviation along the specified dimension.
369
+ #
370
+ # This will force integer types to float64 dtype.
371
+ #
372
+ # @see #inject_rank
373
+ #
374
+ def std(dimen=0)
375
+ variance(dimen).map! { |e| Math.sqrt(e) }
376
+ end
377
+
378
+
379
+ #
380
+ # call-seq:
381
+ # abs_dtype -> Symbol
382
+ #
383
+ # Returns the dtype of the result of a call to #abs. In most cases, this is the same as dtype; it should only differ
384
+ # for :complex64 (where it's :float32) and :complex128 (:float64).
385
+ def abs_dtype
386
+ if self.dtype == :complex64
387
+ :float32
388
+ elsif self.dtype == :complex128
389
+ :float64
390
+ else
391
+ self.dtype
392
+ end
393
+ end
394
+
395
+
396
+ #
397
+ # call-seq:
398
+ # abs -> NMatrix
399
+ #
400
+ # Maps all values in a matrix to their absolute values.
401
+ def abs
402
+ if stype == :dense
403
+ self.__dense_map__ { |v| v.abs }
404
+ elsif stype == :list
405
+ # FIXME: Need __list_map_stored__, but this will do for now.
406
+ self.__list_map_merged_stored__(nil, nil) { |v,dummy| v.abs }
407
+ else
408
+ self.__yale_map_stored__ { |v| v.abs }
409
+ end.cast(self.stype, abs_dtype)
410
+ end
411
+
412
+ alias :permute_columns :laswp
413
+ alias :permute_columns! :laswp!
414
+
415
+ protected
416
+ # Define the element-wise operations for lists. Note that the __list_map_merged_stored__ iterator returns a Ruby Object
417
+ # matrix, which we then cast back to the appropriate type. If you don't want that, you can redefine these functions in
418
+ # your own code.
419
+ {add: :+, sub: :-, mul: :*, div: :/, pow: :**, mod: :%}.each_pair do |ewop, op|
420
+ define_method("__list_elementwise_#{ewop}__") do |rhs|
421
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
422
+ end
423
+ define_method("__dense_elementwise_#{ewop}__") do |rhs|
424
+ self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
425
+ end
426
+ define_method("__yale_elementwise_#{ewop}__") do |rhs|
427
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, rhs.dtype))
428
+ end
429
+ define_method("__list_scalar_#{ewop}__") do |rhs|
430
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
431
+ end
432
+ define_method("__yale_scalar_#{ewop}__") do |rhs|
433
+ self.__yale_map_stored__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
434
+ end
435
+ define_method("__dense_scalar_#{ewop}__") do |rhs|
436
+ self.__dense_map__ { |l| l.send(op,rhs) }.cast(stype, NMatrix.upcast(dtype, NMatrix.min_dtype(rhs)))
437
+ end
438
+ end
439
+
440
+ # Equality operators do not involve a cast. We want to get back matrices of TrueClass and FalseClass.
441
+ {eqeq: :==, neq: :!=, lt: :<, gt: :>, leq: :<=, geq: :>=}.each_pair do |ewop, op|
442
+ define_method("__list_elementwise_#{ewop}__") do |rhs|
443
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
444
+ end
445
+ define_method("__dense_elementwise_#{ewop}__") do |rhs|
446
+ self.__dense_map_pair__(rhs) { |l,r| l.send(op,r) }
447
+ end
448
+ define_method("__yale_elementwise_#{ewop}__") do |rhs|
449
+ self.__yale_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
450
+ end
451
+
452
+ define_method("__list_scalar_#{ewop}__") do |rhs|
453
+ self.__list_map_merged_stored__(rhs, nil) { |l,r| l.send(op,r) }
454
+ end
455
+ define_method("__yale_scalar_#{ewop}__") do |rhs|
456
+ self.__yale_map_stored__ { |l| l.send(op,rhs) }
457
+ end
458
+ define_method("__dense_scalar_#{ewop}__") do |rhs|
459
+ self.__dense_map__ { |l| l.send(op,rhs) }
460
+ end
461
+ end
462
+ end