nmatrix 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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