nmatrix 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/Gemfile +5 -0
- data/History.txt +97 -0
- data/Manifest.txt +34 -7
- data/README.rdoc +13 -13
- data/Rakefile +36 -26
- data/ext/nmatrix/data/data.cpp +15 -2
- data/ext/nmatrix/data/data.h +4 -0
- data/ext/nmatrix/data/ruby_object.h +5 -14
- data/ext/nmatrix/extconf.rb +3 -2
- data/ext/nmatrix/{util/math.cpp → math.cpp} +296 -6
- data/ext/nmatrix/math/asum.h +143 -0
- data/ext/nmatrix/math/geev.h +82 -0
- data/ext/nmatrix/math/gemm.h +267 -0
- data/ext/nmatrix/math/gemv.h +208 -0
- data/ext/nmatrix/math/ger.h +96 -0
- data/ext/nmatrix/math/gesdd.h +80 -0
- data/ext/nmatrix/math/gesvd.h +78 -0
- data/ext/nmatrix/math/getf2.h +86 -0
- data/ext/nmatrix/math/getrf.h +240 -0
- data/ext/nmatrix/math/getri.h +107 -0
- data/ext/nmatrix/math/getrs.h +125 -0
- data/ext/nmatrix/math/idamax.h +86 -0
- data/ext/nmatrix/{util → math}/lapack.h +60 -356
- data/ext/nmatrix/math/laswp.h +165 -0
- data/ext/nmatrix/math/long_dtype.h +52 -0
- data/ext/nmatrix/math/math.h +1154 -0
- data/ext/nmatrix/math/nrm2.h +181 -0
- data/ext/nmatrix/math/potrs.h +125 -0
- data/ext/nmatrix/math/rot.h +141 -0
- data/ext/nmatrix/math/rotg.h +115 -0
- data/ext/nmatrix/math/scal.h +73 -0
- data/ext/nmatrix/math/swap.h +73 -0
- data/ext/nmatrix/math/trsm.h +383 -0
- data/ext/nmatrix/nmatrix.cpp +176 -152
- data/ext/nmatrix/nmatrix.h +1 -2
- data/ext/nmatrix/ruby_constants.cpp +9 -4
- data/ext/nmatrix/ruby_constants.h +1 -0
- data/ext/nmatrix/storage/dense.cpp +57 -41
- data/ext/nmatrix/storage/list.cpp +52 -50
- data/ext/nmatrix/storage/storage.cpp +59 -43
- data/ext/nmatrix/storage/yale.cpp +352 -333
- data/ext/nmatrix/storage/yale.h +4 -0
- data/lib/nmatrix.rb +2 -2
- data/lib/nmatrix/blas.rb +4 -4
- data/lib/nmatrix/enumerate.rb +241 -0
- data/lib/nmatrix/lapack.rb +54 -1
- data/lib/nmatrix/math.rb +462 -0
- data/lib/nmatrix/nmatrix.rb +210 -486
- data/lib/nmatrix/nvector.rb +0 -62
- data/lib/nmatrix/rspec.rb +75 -0
- data/lib/nmatrix/shortcuts.rb +136 -108
- data/lib/nmatrix/version.rb +1 -1
- data/spec/blas_spec.rb +20 -12
- data/spec/elementwise_spec.rb +22 -13
- data/spec/io_spec.rb +1 -0
- data/spec/lapack_spec.rb +197 -0
- data/spec/nmatrix_spec.rb +39 -38
- data/spec/nvector_spec.rb +3 -9
- data/spec/rspec_monkeys.rb +29 -0
- data/spec/rspec_spec.rb +34 -0
- data/spec/shortcuts_spec.rb +14 -16
- data/spec/slice_spec.rb +242 -186
- data/spec/spec_helper.rb +19 -0
- metadata +33 -5
- data/ext/nmatrix/util/math.h +0 -2612
data/ext/nmatrix/storage/yale.h
CHANGED
@@ -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?
|
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
|
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
|
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
|
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
|
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
|
data/lib/nmatrix/lapack.rb
CHANGED
@@ -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
|
data/lib/nmatrix/math.rb
ADDED
@@ -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
|