nmatrix 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/nmatrix/data/ruby_object.h +1 -1
- data/ext/nmatrix/math.cpp +274 -33
- data/ext/nmatrix/math/math.h +8 -2
- data/ext/nmatrix/ruby_nmatrix.c +81 -65
- data/lib/nmatrix/blas.rb +6 -2
- data/lib/nmatrix/cruby/math.rb +744 -0
- data/lib/nmatrix/enumerate.rb +3 -2
- data/lib/nmatrix/jruby/decomposition.rb +24 -0
- data/lib/nmatrix/jruby/enumerable.rb +13 -0
- data/lib/nmatrix/jruby/error.rb +4 -0
- data/lib/nmatrix/jruby/math.rb +501 -0
- data/lib/nmatrix/jruby/nmatrix_java.rb +840 -0
- data/lib/nmatrix/jruby/operators.rb +283 -0
- data/lib/nmatrix/jruby/slice.rb +264 -0
- data/lib/nmatrix/math.rb +233 -635
- data/lib/nmatrix/mkmf.rb +6 -9
- data/lib/nmatrix/monkeys.rb +2 -4
- data/lib/nmatrix/nmatrix.rb +62 -32
- data/lib/nmatrix/shortcuts.rb +8 -3
- data/lib/nmatrix/version.rb +1 -1
- data/spec/00_nmatrix_spec.rb +110 -3
- data/spec/01_enum_spec.rb +7 -1
- data/spec/02_slice_spec.rb +19 -1
- data/spec/03_nmatrix_monkeys_spec.rb +2 -0
- data/spec/elementwise_spec.rb +10 -2
- data/spec/homogeneous_spec.rb +1 -0
- data/spec/io_spec.rb +11 -1
- data/spec/math_spec.rb +346 -102
- data/spec/rspec_spec.rb +1 -0
- data/spec/shortcuts_spec.rb +47 -23
- data/spec/slice_set_spec.rb +7 -2
- data/spec/stat_spec.rb +11 -0
- metadata +20 -41
- data/ext/nmatrix/ttable_helper.rb +0 -115
data/lib/nmatrix/math.rb
CHANGED
@@ -65,8 +65,8 @@ class NMatrix
|
|
65
65
|
# call-seq:
|
66
66
|
# invert! -> NMatrix
|
67
67
|
#
|
68
|
-
# Use LAPACK to calculate the inverse of the matrix (in-place) if available.
|
69
|
-
# Only works on dense matrices. Alternatively uses in-place Gauss-Jordan
|
68
|
+
# Use LAPACK to calculate the inverse of the matrix (in-place) if available.
|
69
|
+
# Only works on dense matrices. Alternatively uses in-place Gauss-Jordan
|
70
70
|
# elimination.
|
71
71
|
#
|
72
72
|
# * *Raises* :
|
@@ -112,315 +112,174 @@ class NMatrix
|
|
112
112
|
end
|
113
113
|
alias :inverse :invert
|
114
114
|
|
115
|
-
#
|
116
115
|
# call-seq:
|
117
|
-
#
|
116
|
+
# exact_inverse! -> NMatrix
|
118
117
|
#
|
119
|
-
#
|
120
|
-
# Only works on dense matrices.
|
118
|
+
# Calulates inverse_exact of a matrix of size 2 or 3.
|
119
|
+
# Only works on dense matrices.
|
121
120
|
#
|
122
121
|
# * *Raises* :
|
123
|
-
# - +
|
124
|
-
# - +
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
122
|
+
# - +DataTypeError+ -> cannot invert an integer matrix in-place.
|
123
|
+
# - +NotImplementedError+ -> cannot find exact inverse of matrix with size greater than 3 #
|
124
|
+
def exact_inverse!
|
125
|
+
raise(ShapeError, "Cannot invert non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
|
126
|
+
raise(DataTypeError, "Cannot invert an integer matrix in-place") if self.integer_dtype?
|
127
|
+
#No internal implementation of getri, so use this other function
|
128
|
+
n = self.shape[0]
|
129
|
+
if n>3
|
130
|
+
raise(NotImplementedError, "Cannot find exact inverse of matrix of size greater than 3")
|
131
|
+
else
|
132
|
+
clond=self.clone
|
133
|
+
__inverse_exact__(clond, n, n)
|
134
|
+
end
|
135
135
|
end
|
136
|
-
alias :adjoint! :adjugate!
|
137
136
|
|
138
137
|
#
|
139
138
|
# call-seq:
|
140
|
-
#
|
139
|
+
# exact_inverse -> NMatrix
|
141
140
|
#
|
142
|
-
# Make a copy of the matrix
|
143
|
-
# Only works on dense matrices.
|
141
|
+
# Make a copy of the matrix, then invert using exact_inverse
|
144
142
|
#
|
145
143
|
# * *Returns* :
|
146
144
|
# - A dense NMatrix. Will be the same type as the input NMatrix,
|
147
145
|
# except if the input is an integral dtype, in which case it will be a
|
148
|
-
# :float64 NMatrix.
|
146
|
+
# :float64 NMatrix.
|
149
147
|
#
|
150
148
|
# * *Raises* :
|
151
149
|
# - +StorageTypeError+ -> only implemented on dense matrices.
|
152
150
|
# - +ShapeError+ -> matrix must be square.
|
151
|
+
# - +NotImplementedError+ -> cannot find exact inverse of matrix with size greater than 3
|
153
152
|
#
|
154
|
-
def
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
153
|
+
def exact_inverse
|
154
|
+
#write this in terms of exact_inverse! so plugins will only have to overwrite
|
155
|
+
#exact_inverse! and not exact_inverse
|
156
|
+
if self.integer_dtype?
|
157
|
+
cloned = self.cast(dtype: :float64)
|
158
|
+
cloned.exact_inverse!
|
159
|
+
else
|
160
|
+
cloned = self.clone
|
161
|
+
cloned.exact_inverse!
|
162
|
+
end
|
161
163
|
end
|
162
|
-
alias :
|
164
|
+
alias :invert_exactly :exact_inverse
|
163
165
|
|
164
|
-
#
|
165
|
-
# call-seq:
|
166
|
-
# getrf! -> Array
|
167
|
-
#
|
168
|
-
# LU factorization of a general M-by-N matrix +A+ using partial pivoting with
|
169
|
-
# row interchanges. The LU factorization is A = PLU, where P is a row permutation
|
170
|
-
# matrix, L is a lower triangular matrix with unit diagonals, and U is an upper
|
171
|
-
# triangular matrix (note that this convention is different from the
|
172
|
-
# clapack_getrf behavior, but matches the standard LAPACK getrf).
|
173
|
-
# +A+ is overwritten with the elements of L and U (the unit
|
174
|
-
# diagonal elements of L are not saved). P is not returned directly and must be
|
175
|
-
# constructed from the pivot array ipiv. The row indices in ipiv are indexed
|
176
|
-
# starting from 1.
|
177
|
-
# Only works for dense matrices.
|
178
|
-
#
|
179
|
-
# * *Returns* :
|
180
|
-
# - The IPIV vector. The L and U matrices are stored in A.
|
181
|
-
# * *Raises* :
|
182
|
-
# - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
|
183
|
-
#
|
184
|
-
def getrf!
|
185
|
-
raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
|
186
|
-
|
187
|
-
#For row-major matrices, clapack_getrf uses a different convention than
|
188
|
-
#described above (U has unit diagonal elements instead of L and columns
|
189
|
-
#are interchanged rather than rows). For column-major matrices, clapack
|
190
|
-
#uses the stanard conventions. So we just transpose the matrix before
|
191
|
-
#and after calling clapack_getrf.
|
192
|
-
#Unfortunately, this is not a very good way, uses a lot of memory.
|
193
|
-
temp = self.transpose
|
194
|
-
ipiv = NMatrix::LAPACK::clapack_getrf(:col, self.shape[0], self.shape[1], temp, self.shape[0])
|
195
|
-
temp = temp.transpose
|
196
|
-
self[0...self.shape[0], 0...self.shape[1]] = temp
|
197
|
-
|
198
|
-
#for some reason, in clapack_getrf, the indices in ipiv start from 0
|
199
|
-
#instead of 1 as in LAPACK.
|
200
|
-
ipiv.each_index { |i| ipiv[i]+=1 }
|
201
|
-
|
202
|
-
return ipiv
|
203
|
-
end
|
204
166
|
|
205
|
-
#
|
206
|
-
# call-seq:
|
207
|
-
# geqrf! -> shape.min x 1 NMatrix
|
208
|
-
#
|
209
|
-
# QR factorization of a general M-by-N matrix +A+.
|
210
|
-
#
|
211
|
-
# The QR factorization is A = QR, where Q is orthogonal and R is Upper Triangular
|
212
|
-
# +A+ is overwritten with the elements of R and Q with Q being represented by the
|
213
|
-
# elements below A's diagonal and an array of scalar factors in the output NMatrix.
|
214
|
-
#
|
215
|
-
# The matrix Q is represented as a product of elementary reflectors
|
216
|
-
# Q = H(1) H(2) . . . H(k), where k = min(m,n).
|
217
|
-
#
|
218
|
-
# Each H(i) has the form
|
219
|
-
#
|
220
|
-
# H(i) = I - tau * v * v'
|
221
|
-
#
|
222
|
-
# http://www.netlib.org/lapack/explore-html/d3/d69/dgeqrf_8f.html
|
223
|
-
#
|
224
|
-
# Only works for dense matrices.
|
225
|
-
#
|
226
|
-
# * *Returns* :
|
227
|
-
# - Vector TAU. Q and R are stored in A. Q is represented by TAU and A
|
228
|
-
# * *Raises* :
|
229
|
-
# - +StorageTypeError+ -> LAPACK functions only work on dense matrices.
|
230
|
-
#
|
231
|
-
def geqrf!
|
232
|
-
# The real implementation is in lib/nmatrix/lapacke.rb
|
233
|
-
raise(NotImplementedError, "geqrf! requires the nmatrix-lapacke gem")
|
234
|
-
end
|
235
|
-
|
236
|
-
#
|
237
|
-
# call-seq:
|
238
|
-
# ormqr(tau) -> NMatrix
|
239
|
-
# ormqr(tau, side, transpose, c) -> NMatrix
|
240
|
-
#
|
241
|
-
# Returns the product Q * c or c * Q after a call to geqrf! used in QR factorization.
|
242
|
-
# +c+ is overwritten with the elements of the result NMatrix if supplied. Q is the orthogonal matrix
|
243
|
-
# represented by tau and the calling NMatrix
|
244
|
-
#
|
245
|
-
# Only works on float types, use unmqr for complex types.
|
246
|
-
#
|
247
|
-
# == Arguments
|
248
|
-
#
|
249
|
-
# * +tau+ - vector containing scalar factors of elementary reflectors
|
250
|
-
# * +side+ - direction of multiplication [:left, :right]
|
251
|
-
# * +transpose+ - apply Q with or without transpose [false, :transpose]
|
252
|
-
# * +c+ - NMatrix multplication argument that is overwritten, no argument assumes c = identity
|
253
|
-
#
|
254
|
-
# * *Returns* :
|
255
|
-
#
|
256
|
-
# - Q * c or c * Q Where Q may be transposed before multiplication.
|
257
|
-
#
|
258
|
-
#
|
259
|
-
# * *Raises* :
|
260
|
-
# - +StorageTypeError+ -> LAPACK functions only work on dense matrices.
|
261
|
-
# - +TypeError+ -> Works only on floating point matrices, use unmqr for complex types
|
262
|
-
# - +TypeError+ -> c must have the same dtype as the calling NMatrix
|
263
|
-
#
|
264
|
-
def ormqr(tau, side=:left, transpose=false, c=nil)
|
265
|
-
# The real implementation is in lib/nmatrix/lapacke.rb
|
266
|
-
raise(NotImplementedError, "ormqr requires the nmatrix-lapacke gem")
|
267
|
-
|
268
|
-
end
|
269
167
|
|
270
168
|
#
|
271
169
|
# call-seq:
|
272
|
-
#
|
273
|
-
# unmqr(tau, side, transpose, c) -> NMatrix
|
170
|
+
# pinv -> NMatrix
|
274
171
|
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
# represented by tau and the calling NMatrix
|
278
|
-
#
|
279
|
-
# Only works on complex types, use ormqr for float types.
|
172
|
+
# Compute the Moore-Penrose pseudo-inverse of a matrix using its
|
173
|
+
# singular value decomposition (SVD).
|
280
174
|
#
|
281
|
-
#
|
175
|
+
# This function requires the nmatrix-atlas gem installed.
|
282
176
|
#
|
283
|
-
# *
|
284
|
-
#
|
285
|
-
# * +transpose+ - apply Q as Q or its complex conjugate [false, :complex_conjugate]
|
286
|
-
# * +c+ - NMatrix multplication argument that is overwritten, no argument assumes c = identity
|
177
|
+
# * *Arguments* :
|
178
|
+
# - +tolerance(optional)+ -> Cutoff for small singular values.
|
287
179
|
#
|
288
180
|
# * *Returns* :
|
289
|
-
#
|
290
|
-
# - Q * c or c * Q Where Q may be transformed to its complex conjugate before multiplication.
|
291
|
-
#
|
181
|
+
# - Pseudo-inverse matrix.
|
292
182
|
#
|
293
183
|
# * *Raises* :
|
294
|
-
# - +
|
295
|
-
# - +TypeError+ ->
|
296
|
-
# - +TypeError+ -> c must have the same dtype as the calling NMatrix
|
184
|
+
# - +NotImplementedError+ -> If called without nmatrix-atlas or nmatrix-lapacke gem.
|
185
|
+
# - +TypeError+ -> If called without float or complex data type.
|
297
186
|
#
|
298
|
-
|
299
|
-
# The real implementation is in lib/nmatrix/lapacke.rb
|
300
|
-
raise(NotImplementedError, "unmqr requires the nmatrix-lapacke gem")
|
301
|
-
end
|
302
|
-
|
187
|
+
# * *Examples* :
|
303
188
|
#
|
304
|
-
#
|
305
|
-
#
|
189
|
+
# a = NMatrix.new([2,2],[1,2,
|
190
|
+
# 3,4], dtype: :float64)
|
191
|
+
# a.pinv # => [ [-2.0000000000000018, 1.0000000000000007]
|
192
|
+
# [1.5000000000000016, -0.5000000000000008] ]
|
306
193
|
#
|
307
|
-
#
|
308
|
-
#
|
309
|
-
# The result will be written in either the upper or lower triangular portion of the
|
310
|
-
# matrix, depending on whether the argument is +:upper+ or +:lower+.
|
311
|
-
# Also the function only reads in the upper or lower part of the matrix,
|
312
|
-
# so it doesn't actually have to be symmetric/Hermitian.
|
313
|
-
# However, if the matrix (i.e. the symmetric matrix implied by the lower/upper
|
314
|
-
# half) is not positive-definite, the function will return nonsense.
|
194
|
+
# b = NMatrix.new([4,1],[1,2,3,4], dtype: :float64)
|
195
|
+
# b.pinv # => [ [ 0.03333333, 0.06666667, 0.99999999, 0.13333333] ]
|
315
196
|
#
|
316
|
-
#
|
317
|
-
# installed.
|
197
|
+
# == References
|
318
198
|
#
|
319
|
-
# *
|
320
|
-
#
|
321
|
-
# * *Raises* :
|
322
|
-
# - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
|
323
|
-
# - +ShapeError+ -> Must be square.
|
324
|
-
# - +NotImplementedError+ -> If called without nmatrix-atlas or nmatrix-lapacke gem
|
199
|
+
# * https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_pseudoinverse
|
200
|
+
# * G. Strang, Linear Algebra and Its Applications, 2nd Ed., Orlando, FL, Academic Press
|
325
201
|
#
|
326
|
-
def
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
202
|
+
def pinv(tolerance = 1e-15)
|
203
|
+
raise DataTypeError, "pinv works only with matrices of float or complex data type" unless
|
204
|
+
[:float32, :float64, :complex64, :complex128].include?(dtype)
|
205
|
+
if self.complex_dtype?
|
206
|
+
u, s, vt = self.complex_conjugate.gesvd # singular value decomposition
|
207
|
+
else
|
208
|
+
u, s, vt = self.gesvd
|
209
|
+
end
|
210
|
+
rows = self.shape[0]
|
211
|
+
cols = self.shape[1]
|
212
|
+
if rows < cols
|
213
|
+
u_reduced = u
|
214
|
+
vt_reduced = vt[0..rows - 1, 0..cols - 1].transpose
|
215
|
+
else
|
216
|
+
u_reduced = u[0..rows - 1, 0..cols - 1]
|
217
|
+
vt_reduced = vt.transpose
|
218
|
+
end
|
219
|
+
largest_singular_value = s.max.to_f
|
220
|
+
cutoff = tolerance * largest_singular_value
|
221
|
+
(0...[rows, cols].min).each do |i|
|
222
|
+
s[i] = 1 / s[i] if s[i] > cutoff
|
223
|
+
s[i] = 0 if s[i] <= cutoff
|
224
|
+
end
|
225
|
+
multiplier = u_reduced.dot(NMatrix.diagonal(s.to_a)).transpose
|
226
|
+
vt_reduced.dot(multiplier)
|
337
227
|
end
|
228
|
+
alias :pseudo_inverse :pinv
|
229
|
+
alias :pseudoinverse :pinv
|
338
230
|
|
339
231
|
|
340
232
|
#
|
341
233
|
# call-seq:
|
342
|
-
#
|
343
|
-
#
|
344
|
-
# Calculates the Cholesky factorization of a matrix and returns the
|
345
|
-
# upper and lower matrices such that A=LU and L=U*, where * is
|
346
|
-
# either the transpose or conjugate transpose.
|
347
|
-
#
|
348
|
-
# Unlike potrf!, this makes method requires that the original is matrix is
|
349
|
-
# symmetric or Hermitian. However, it is still your responsibility to make
|
350
|
-
# sure it is positive-definite.
|
351
|
-
def factorize_cholesky
|
352
|
-
raise "Matrix must be symmetric/Hermitian for Cholesky factorization" unless self.hermitian?
|
353
|
-
l = self.clone.potrf_lower!.tril!
|
354
|
-
u = l.conjugate_transpose
|
355
|
-
[u,l]
|
356
|
-
end
|
357
|
-
|
234
|
+
# adjugate! -> NMatrix
|
358
235
|
#
|
359
|
-
#
|
360
|
-
#
|
236
|
+
# Calculate the adjugate of the matrix (in-place).
|
237
|
+
# Only works on dense matrices.
|
361
238
|
#
|
362
|
-
#
|
363
|
-
#
|
364
|
-
#
|
365
|
-
#
|
366
|
-
#
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
t = self.clone
|
376
|
-
pivot = t.getrf!
|
377
|
-
return t unless with_permutation_matrix
|
378
|
-
|
379
|
-
[t, FactorizeLUMethods.permutation_matrix_from(pivot)]
|
239
|
+
# * *Raises* :
|
240
|
+
# - +StorageTypeError+ -> only implemented on dense matrices.
|
241
|
+
# - +ShapeError+ -> matrix must be square.
|
242
|
+
# - +DataTypeError+ -> cannot calculate adjugate of an integer matrix in-place.
|
243
|
+
#
|
244
|
+
def adjugate!
|
245
|
+
raise(StorageTypeError, "adjugate only works on dense matrices currently") unless self.dense?
|
246
|
+
raise(ShapeError, "Cannot calculate adjugate of a non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
|
247
|
+
raise(DataTypeError, "Cannot calculate adjugate of an integer matrix in-place") if self.integer_dtype?
|
248
|
+
d = self.det
|
249
|
+
self.invert!
|
250
|
+
self.map! { |e| e * d }
|
251
|
+
self
|
380
252
|
end
|
253
|
+
alias :adjoint! :adjugate!
|
381
254
|
|
382
255
|
#
|
383
256
|
# call-seq:
|
384
|
-
#
|
385
|
-
#
|
386
|
-
# QR factorization of a matrix without column pivoting.
|
387
|
-
# Q is orthogonal and R is upper triangular if input is square or upper trapezoidal if
|
388
|
-
# input is rectangular.
|
257
|
+
# adjugate -> NMatrix
|
389
258
|
#
|
390
|
-
#
|
259
|
+
# Make a copy of the matrix and calculate the adjugate of the matrix.
|
260
|
+
# Only works on dense matrices.
|
391
261
|
#
|
392
262
|
# * *Returns* :
|
393
|
-
# -
|
263
|
+
# - A dense NMatrix. Will be the same type as the input NMatrix,
|
264
|
+
# except if the input is an integral dtype, in which case it will be a
|
265
|
+
# :float64 NMatrix.
|
394
266
|
#
|
395
267
|
# * *Raises* :
|
396
|
-
# - +StorageTypeError+ -> only implemented
|
397
|
-
# - +ShapeError+ ->
|
398
|
-
#
|
399
|
-
def
|
400
|
-
raise(
|
401
|
-
raise(ShapeError, "
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
#Obtain Q
|
408
|
-
q = self.complex_dtype? ? r.unmqr(tau) : r.ormqr(tau)
|
409
|
-
|
410
|
-
#Obtain R
|
411
|
-
if rows <= columns
|
412
|
-
r.upper_triangle!
|
413
|
-
#Need to account for upper trapezoidal structure if R is a tall rectangle (rows > columns)
|
414
|
-
else
|
415
|
-
r[0...columns, 0...columns].upper_triangle!
|
416
|
-
r[columns...rows, 0...columns] = 0
|
417
|
-
end
|
418
|
-
|
419
|
-
[q,r]
|
268
|
+
# - +StorageTypeError+ -> only implemented on dense matrices.
|
269
|
+
# - +ShapeError+ -> matrix must be square.
|
270
|
+
#
|
271
|
+
def adjugate
|
272
|
+
raise(StorageTypeError, "adjugate only works on dense matrices currently") unless self.dense?
|
273
|
+
raise(ShapeError, "Cannot calculate adjugate of a non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
|
274
|
+
d = self.det
|
275
|
+
mat = self.invert
|
276
|
+
mat.map! { |e| e * d }
|
277
|
+
mat
|
420
278
|
end
|
279
|
+
alias :adjoint :adjugate
|
421
280
|
|
422
281
|
# Reduce self to upper hessenberg form using householder transforms.
|
423
|
-
#
|
282
|
+
#
|
424
283
|
# == References
|
425
284
|
#
|
426
285
|
# * http://en.wikipedia.org/wiki/Hessenberg_matrix
|
@@ -431,331 +290,76 @@ class NMatrix
|
|
431
290
|
|
432
291
|
# Destructive version of #hessenberg
|
433
292
|
def hessenberg!
|
434
|
-
raise ShapeError, "Trying to reduce non 2D matrix to hessenberg form" if
|
293
|
+
raise ShapeError, "Trying to reduce non 2D matrix to hessenberg form" if
|
435
294
|
shape.size != 2
|
436
|
-
raise ShapeError, "Trying to reduce non-square matrix to hessenberg form" if
|
295
|
+
raise ShapeError, "Trying to reduce non-square matrix to hessenberg form" if
|
437
296
|
shape[0] != shape[1]
|
438
297
|
raise StorageTypeError, "Matrix must be dense" if stype != :dense
|
439
|
-
raise TypeError, "Works with float matrices only" unless
|
298
|
+
raise TypeError, "Works with float matrices only" unless
|
440
299
|
[:float64,:float32].include?(dtype)
|
441
300
|
|
442
301
|
__hessenberg__(self)
|
443
302
|
self
|
444
303
|
end
|
445
304
|
|
446
|
-
# Solve the matrix equation AX = B, where A is +self+, B is the first
|
447
|
-
# argument, and X is returned. A must be a nxn square matrix, while B must be
|
448
|
-
# nxm. Only works with dense matrices and non-integer, non-object data types.
|
449
|
-
#
|
450
|
-
# == Arguments
|
451
|
-
#
|
452
|
-
# * +b+ - the right hand side
|
453
|
-
#
|
454
|
-
# == Options
|
455
|
-
#
|
456
|
-
# * +form+ - Signifies the form of the matrix A in the linear system AX=B.
|
457
|
-
# If not set then it defaults to +:general+, which uses an LU solver.
|
458
|
-
# Other possible values are +:lower_tri+, +:upper_tri+ and +:pos_def+ (alternatively,
|
459
|
-
# non-abbreviated symbols +:lower_triangular+, +:upper_triangular+,
|
460
|
-
# and +:positive_definite+ can be used.
|
461
|
-
# If +:lower_tri+ or +:upper_tri+ is set, then a specialized linear solver for linear
|
462
|
-
# systems AX=B with a lower or upper triangular matrix A is used. If +:pos_def+ is chosen,
|
463
|
-
# then the linear system is solved via the Cholesky factorization.
|
464
|
-
# Note that when +:lower_tri+ or +:upper_tri+ is used, then the algorithm just assumes that
|
465
|
-
# all entries in the lower/upper triangle of the matrix are zeros without checking (which
|
466
|
-
# can be useful in certain applications).
|
467
|
-
#
|
468
|
-
#
|
469
|
-
# == Usage
|
470
|
-
#
|
471
|
-
# a = NMatrix.new [2,2], [3,1,1,2], dtype: dtype
|
472
|
-
# b = NMatrix.new [2,1], [9,8], dtype: dtype
|
473
|
-
# a.solve(b)
|
474
|
-
#
|
475
|
-
# # solve an upper triangular linear system more efficiently:
|
476
|
-
# require 'benchmark'
|
477
|
-
# require 'nmatrix/lapacke'
|
478
|
-
# rand_mat = NMatrix.random([10000, 10000], dtype: :float64)
|
479
|
-
# a = rand_mat.triu
|
480
|
-
# b = NMatrix.random([10000, 10], dtype: :float64)
|
481
|
-
# Benchmark.bm(10) do |bm|
|
482
|
-
# bm.report('general') { a.solve(b) }
|
483
|
-
# bm.report('upper_tri') { a.solve(b, form: :upper_tri) }
|
484
|
-
# end
|
485
|
-
# # user system total real
|
486
|
-
# # general 73.170000 0.670000 73.840000 ( 73.810086)
|
487
|
-
# # upper_tri 0.180000 0.000000 0.180000 ( 0.182491)
|
488
|
-
#
|
489
|
-
def solve(b, opts = {})
|
490
|
-
raise(ShapeError, "Must be called on square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
|
491
|
-
raise(ShapeError, "number of rows of b must equal number of cols of self") if
|
492
|
-
self.shape[1] != b.shape[0]
|
493
|
-
raise(ArgumentError, "only works with dense matrices") if self.stype != :dense
|
494
|
-
raise(ArgumentError, "only works for non-integer, non-object dtypes") if
|
495
|
-
integer_dtype? or object_dtype? or b.integer_dtype? or b.object_dtype?
|
496
|
-
|
497
|
-
opts = { form: :general }.merge(opts)
|
498
|
-
x = b.clone
|
499
|
-
n = self.shape[0]
|
500
|
-
nrhs = b.shape[1]
|
501
|
-
|
502
|
-
case opts[:form]
|
503
|
-
when :general
|
504
|
-
clone = self.clone
|
505
|
-
ipiv = NMatrix::LAPACK.clapack_getrf(:row, n, n, clone, n)
|
506
|
-
# When we call clapack_getrs with :row, actually only the first matrix
|
507
|
-
# (i.e. clone) is interpreted as row-major, while the other matrix (x)
|
508
|
-
# is interpreted as column-major. See here: http://math-atlas.sourceforge.net/faq.html#RowSolve
|
509
|
-
# So we must transpose x before and after
|
510
|
-
# calling it.
|
511
|
-
x = x.transpose
|
512
|
-
NMatrix::LAPACK.clapack_getrs(:row, :no_transpose, n, nrhs, clone, n, ipiv, x, n)
|
513
|
-
x.transpose
|
514
|
-
when :upper_tri, :upper_triangular
|
515
|
-
raise(ArgumentError, "upper triangular solver does not work with complex dtypes") if
|
516
|
-
complex_dtype? or b.complex_dtype?
|
517
|
-
# this is the correct function call; see https://github.com/SciRuby/nmatrix/issues/374
|
518
|
-
NMatrix::BLAS::cblas_trsm(:row, :left, :upper, false, :nounit, n, nrhs, 1.0, self, n, x, nrhs)
|
519
|
-
x
|
520
|
-
when :lower_tri, :lower_triangular
|
521
|
-
raise(ArgumentError, "lower triangular solver does not work with complex dtypes") if
|
522
|
-
complex_dtype? or b.complex_dtype?
|
523
|
-
NMatrix::BLAS::cblas_trsm(:row, :left, :lower, false, :nounit, n, nrhs, 1.0, self, n, x, nrhs)
|
524
|
-
x
|
525
|
-
when :pos_def, :positive_definite
|
526
|
-
u, l = self.factorize_cholesky
|
527
|
-
z = l.solve(b, form: :lower_tri)
|
528
|
-
u.solve(z, form: :upper_tri)
|
529
|
-
else
|
530
|
-
raise(ArgumentError, "#{opts[:form]} is not a valid form option")
|
531
|
-
end
|
532
|
-
end
|
533
|
-
|
534
|
-
#
|
535
|
-
# call-seq:
|
536
|
-
# gesvd! -> [u, sigma, v_transpose]
|
537
|
-
# gesvd! -> [u, sigma, v_conjugate_transpose] # complex
|
538
|
-
#
|
539
|
-
# Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
|
540
|
-
# This is destructive, modifying the source NMatrix. See also #gesdd.
|
541
|
-
#
|
542
|
-
# Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
|
543
|
-
# requires.
|
544
|
-
#
|
545
|
-
def gesvd!(workspace_size=1)
|
546
|
-
NMatrix::LAPACK::gesvd(self, workspace_size)
|
547
|
-
end
|
548
|
-
|
549
|
-
#
|
550
|
-
# call-seq:
|
551
|
-
# gesvd -> [u, sigma, v_transpose]
|
552
|
-
# gesvd -> [u, sigma, v_conjugate_transpose] # complex
|
553
|
-
#
|
554
|
-
# Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
|
555
|
-
#
|
556
|
-
# Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
|
557
|
-
# requires.
|
558
|
-
#
|
559
|
-
def gesvd(workspace_size=1)
|
560
|
-
self.clone.gesvd!(workspace_size)
|
561
|
-
end
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
#
|
566
|
-
# call-seq:
|
567
|
-
# gesdd! -> [u, sigma, v_transpose]
|
568
|
-
# gesdd! -> [u, sigma, v_conjugate_transpose] # complex
|
569
|
-
#
|
570
|
-
# Compute the singular value decomposition of a matrix using LAPACK's GESDD function. This uses a divide-and-conquer
|
571
|
-
# strategy. This is destructive, modifying the source NMatrix. See also #gesvd.
|
572
|
-
#
|
573
|
-
# Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
|
574
|
-
# requires.
|
575
|
-
#
|
576
|
-
def gesdd!(workspace_size=nil)
|
577
|
-
NMatrix::LAPACK::gesdd(self, workspace_size)
|
578
|
-
end
|
579
|
-
|
580
|
-
#
|
581
|
-
# call-seq:
|
582
|
-
# gesdd -> [u, sigma, v_transpose]
|
583
|
-
# gesdd -> [u, sigma, v_conjugate_transpose] # complex
|
584
|
-
#
|
585
|
-
# Compute the singular value decomposition of a matrix using LAPACK's GESDD function. This uses a divide-and-conquer
|
586
|
-
# strategy. See also #gesvd.
|
587
|
-
#
|
588
|
-
# Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
|
589
|
-
# requires.
|
590
|
-
#
|
591
|
-
def gesdd(workspace_size=nil)
|
592
|
-
self.clone.gesdd!(workspace_size)
|
593
|
-
end
|
594
|
-
|
595
|
-
#
|
596
|
-
# call-seq:
|
597
|
-
# laswp!(ary) -> NMatrix
|
598
|
-
#
|
599
|
-
# In-place permute the columns of a dense matrix using LASWP according to the order given as an array +ary+.
|
600
|
-
#
|
601
|
-
# If +:convention+ is +:lapack+, then +ary+ represents a sequence of pair-wise permutations which are
|
602
|
-
# performed successively. That is, the i'th entry of +ary+ is the index of the column to swap
|
603
|
-
# the i'th column with, having already applied all earlier swaps.
|
604
|
-
#
|
605
|
-
# If +:convention+ is +:intuitive+, then +ary+ represents the order of columns after the permutation.
|
606
|
-
# That is, the i'th entry of +ary+ is the index of the column that will be in position i after the
|
607
|
-
# reordering (Matlab-like behaviour). This is the default.
|
608
|
-
#
|
609
|
-
# Not yet implemented for yale or list.
|
610
|
-
#
|
611
|
-
# == Arguments
|
612
|
-
#
|
613
|
-
# * +ary+ - An Array specifying the order of the columns. See above for details.
|
614
|
-
#
|
615
|
-
# == Options
|
616
|
-
#
|
617
|
-
# * +:covention+ - Possible values are +:lapack+ and +:intuitive+. Default is +:intuitive+. See above for details.
|
618
|
-
#
|
619
|
-
def laswp!(ary, opts={})
|
620
|
-
raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
|
621
|
-
opts = { convention: :intuitive }.merge(opts)
|
622
|
-
|
623
|
-
if opts[:convention] == :intuitive
|
624
|
-
if ary.length != ary.uniq.length
|
625
|
-
raise(ArgumentError, "No duplicated entries in the order array are allowed under convention :intuitive")
|
626
|
-
end
|
627
|
-
n = self.shape[1]
|
628
|
-
p = []
|
629
|
-
order = (0...n).to_a
|
630
|
-
0.upto(n-2) do |i|
|
631
|
-
p[i] = order.index(ary[i])
|
632
|
-
order[i], order[p[i]] = order[p[i]], order[i]
|
633
|
-
end
|
634
|
-
p[n-1] = n-1
|
635
|
-
else
|
636
|
-
p = ary
|
637
|
-
end
|
638
|
-
|
639
|
-
NMatrix::LAPACK::laswp(self, p)
|
640
|
-
end
|
641
|
-
|
642
|
-
#
|
643
|
-
# call-seq:
|
644
|
-
# laswp(ary) -> NMatrix
|
645
|
-
#
|
646
|
-
# Permute the columns of a dense matrix using LASWP according to the order given in an array +ary+.
|
647
|
-
#
|
648
|
-
# If +:convention+ is +:lapack+, then +ary+ represents a sequence of pair-wise permutations which are
|
649
|
-
# performed successively. That is, the i'th entry of +ary+ is the index of the column to swap
|
650
|
-
# the i'th column with, having already applied all earlier swaps. This is the default.
|
651
|
-
#
|
652
|
-
# If +:convention+ is +:intuitive+, then +ary+ represents the order of columns after the permutation.
|
653
|
-
# That is, the i'th entry of +ary+ is the index of the column that will be in position i after the
|
654
|
-
# reordering (Matlab-like behaviour).
|
655
|
-
#
|
656
|
-
# Not yet implemented for yale or list.
|
657
|
-
#
|
658
|
-
# == Arguments
|
659
|
-
#
|
660
|
-
# * +ary+ - An Array specifying the order of the columns. See above for details.
|
661
|
-
#
|
662
|
-
# == Options
|
663
|
-
#
|
664
|
-
# * +:covention+ - Possible values are +:lapack+ and +:intuitive+. Default is +:lapack+. See above for details.
|
665
|
-
#
|
666
|
-
def laswp(ary, opts={})
|
667
|
-
self.clone.laswp!(ary, opts)
|
668
|
-
end
|
669
305
|
|
670
|
-
#
|
671
306
|
# call-seq:
|
672
|
-
#
|
673
|
-
#
|
674
|
-
# Calculate the determinant by way of LU decomposition. This is accomplished
|
675
|
-
# using clapack_getrf, and then by taking the product of the diagonal elements. There is a
|
676
|
-
# risk of underflow/overflow.
|
307
|
+
# matrix_norm -> Numeric
|
677
308
|
#
|
678
|
-
#
|
679
|
-
# This method requires making a copy of the matrix, since clapack_getrf
|
680
|
-
# modifies its input.
|
309
|
+
# Calculates the selected norm (defaults to 2-norm) of a 2D matrix.
|
681
310
|
#
|
682
|
-
#
|
311
|
+
# This should be used for small or medium sized matrices.
|
312
|
+
# For greater matrices, there should be a separate implementation where
|
313
|
+
# the norm is estimated rather than computed, for the sake of computation speed.
|
683
314
|
#
|
684
|
-
#
|
685
|
-
#
|
315
|
+
# Currently implemented norms are 1-norm, 2-norm, Frobenius, Infinity.
|
316
|
+
# A minus on the 1, 2 and inf norms returns the minimum instead of the maximum value.
|
686
317
|
#
|
687
|
-
#
|
688
|
-
#
|
318
|
+
# Tested mainly with dense matrices. Further checks and modifications might
|
319
|
+
# be necessary for sparse matrices.
|
689
320
|
#
|
690
321
|
# * *Returns* :
|
691
|
-
#
|
322
|
+
# - The selected norm of the matrix.
|
692
323
|
# * *Raises* :
|
693
|
-
#
|
694
|
-
#
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
prod = num_perm % 2 == 1 ? -1 : 1 # odd permutations => negative
|
712
|
-
[shape[0],shape[1]].min.times do |i|
|
713
|
-
prod *= copy[i,i]
|
324
|
+
# - +NotImplementedError+ -> norm can be calculated only for 2D matrices
|
325
|
+
# - +ArgumentError+ -> unrecognized norm
|
326
|
+
#
|
327
|
+
def matrix_norm type = 2
|
328
|
+
raise(NotImplementedError, "norm can be calculated only for 2D matrices") unless self.dim == 2
|
329
|
+
raise(NotImplementedError, "norm only implemented for dense storage") unless self.stype == :dense
|
330
|
+
raise(ArgumentError, "norm not defined for byte dtype")if self.dtype == :byte
|
331
|
+
case type
|
332
|
+
when nil, 2, -2
|
333
|
+
return self.two_matrix_norm (type == -2)
|
334
|
+
when 1, -1
|
335
|
+
return self.one_matrix_norm (type == -1)
|
336
|
+
when :frobenius, :fro
|
337
|
+
return self.fro_matrix_norm
|
338
|
+
when :infinity, :inf, :'-inf', :'-infinity'
|
339
|
+
return self.inf_matrix_norm (type == :'-inf' || type == :'-infinity')
|
340
|
+
else
|
341
|
+
raise ArgumentError.new("argument must be a valid integer or symbol")
|
714
342
|
end
|
715
|
-
|
716
|
-
# Convert back to an integer if necessary
|
717
|
-
new_dtype != self.dtype ? prod.round : prod #prevent rounding errors
|
718
|
-
end
|
719
|
-
|
720
|
-
#
|
721
|
-
# call-seq:
|
722
|
-
# complex_conjugate -> NMatrix
|
723
|
-
# complex_conjugate(new_stype) -> NMatrix
|
724
|
-
#
|
725
|
-
# Get the complex conjugate of this matrix. See also complex_conjugate! for
|
726
|
-
# an in-place operation (provided the dtype is already +:complex64+ or
|
727
|
-
# +:complex128+).
|
728
|
-
#
|
729
|
-
# Doesn't work on list matrices, but you can optionally pass in the stype you
|
730
|
-
# want to cast to if you're dealing with a list matrix.
|
731
|
-
#
|
732
|
-
# * *Arguments* :
|
733
|
-
# - +new_stype+ -> stype for the new matrix.
|
734
|
-
# * *Returns* :
|
735
|
-
# - If the original NMatrix isn't complex, the result is a +:complex128+ NMatrix. Otherwise, it's the original dtype.
|
736
|
-
#
|
737
|
-
def complex_conjugate(new_stype = self.stype)
|
738
|
-
self.cast(new_stype, NMatrix::upcast(dtype, :complex64)).complex_conjugate!
|
739
343
|
end
|
740
344
|
|
741
345
|
# Calculate the variance co-variance matrix
|
742
|
-
#
|
346
|
+
#
|
743
347
|
# == Options
|
744
|
-
#
|
348
|
+
#
|
745
349
|
# * +:for_sample_data+ - Default true. If set to false will consider the denominator for
|
746
350
|
# population data (i.e. N, as opposed to N-1 for sample data).
|
747
|
-
#
|
351
|
+
#
|
748
352
|
# == References
|
749
|
-
#
|
353
|
+
#
|
750
354
|
# * http://stattrek.com/matrix-algebra/covariance-matrix.aspx
|
751
355
|
def cov(opts={})
|
752
356
|
raise TypeError, "Only works for non-integer dtypes" if integer_dtype?
|
753
357
|
opts = {
|
754
358
|
for_sample_data: true
|
755
359
|
}.merge(opts)
|
756
|
-
|
360
|
+
|
757
361
|
denominator = opts[:for_sample_data] ? rows - 1 : rows
|
758
|
-
ones = NMatrix.ones [rows,1]
|
362
|
+
ones = NMatrix.ones [rows,1]
|
759
363
|
deviation_scores = self - ones.dot(ones.transpose).dot(self) / rows
|
760
364
|
deviation_scores.transpose.dot(deviation_scores) / denominator
|
761
365
|
end
|
@@ -769,24 +373,24 @@ class NMatrix
|
|
769
373
|
|
770
374
|
# Raise a square matrix to a power. Be careful of numeric overflows!
|
771
375
|
# In case *n* is 0, an identity matrix of the same dimension is returned. In case
|
772
|
-
# of negative *n*, the matrix is inverted and the absolute value of *n* taken
|
376
|
+
# of negative *n*, the matrix is inverted and the absolute value of *n* taken
|
773
377
|
# for computing the power.
|
774
|
-
#
|
378
|
+
#
|
775
379
|
# == Arguments
|
776
|
-
#
|
380
|
+
#
|
777
381
|
# * +n+ - Integer to which self is to be raised.
|
778
|
-
#
|
382
|
+
#
|
779
383
|
# == References
|
780
|
-
#
|
781
|
-
# * R.G Dromey - How to Solve it by Computer. Link -
|
384
|
+
#
|
385
|
+
# * R.G Dromey - How to Solve it by Computer. Link -
|
782
386
|
# http://www.amazon.com/Solve-Computer-Prentice-Hall-International-Science/dp/0134340019/ref=sr_1_1?ie=UTF8&qid=1422605572&sr=8-1&keywords=how+to+solve+it+by+computer
|
783
387
|
def pow n
|
784
|
-
raise ShapeError, "Only works with 2D square matrices." if
|
388
|
+
raise ShapeError, "Only works with 2D square matrices." if
|
785
389
|
shape[0] != shape[1] or shape.size != 2
|
786
390
|
raise TypeError, "Only works with integer powers" unless n.is_a?(Integer)
|
787
391
|
|
788
392
|
sequence = (integer_dtype? ? self.cast(dtype: :int64) : self).clone
|
789
|
-
product = NMatrix.eye shape[0], dtype: sequence.dtype, stype: sequence.stype
|
393
|
+
product = NMatrix.eye shape[0], dtype: sequence.dtype, stype: sequence.stype
|
790
394
|
|
791
395
|
if n == 0
|
792
396
|
return NMatrix.eye(shape, dtype: dtype, stype: stype)
|
@@ -814,8 +418,8 @@ class NMatrix
|
|
814
418
|
#
|
815
419
|
# * +mat+ - A 2D NMatrix object
|
816
420
|
#
|
817
|
-
# === Usage
|
818
|
-
#
|
421
|
+
# === Usage
|
422
|
+
#
|
819
423
|
# a = NMatrix.new([2,2],[1,2,
|
820
424
|
# 3,4])
|
821
425
|
# b = NMatrix.new([2,3],[1,1,1,
|
@@ -824,7 +428,7 @@ class NMatrix
|
|
824
428
|
# [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
|
825
429
|
# [3.0, 3.0, 3.0, 4.0, 4.0, 4.0]
|
826
430
|
# [3.0, 3.0, 3.0, 4.0, 4.0, 4.0] ]
|
827
|
-
#
|
431
|
+
#
|
828
432
|
def kron_prod(mat)
|
829
433
|
unless self.dimensions==2 and mat.dimensions==2
|
830
434
|
raise ShapeError, "Implemented for 2D NMatrix objects only."
|
@@ -849,21 +453,7 @@ class NMatrix
|
|
849
453
|
end
|
850
454
|
end
|
851
455
|
|
852
|
-
NMatrix.new([n,m], kron_prod_array)
|
853
|
-
end
|
854
|
-
|
855
|
-
#
|
856
|
-
# call-seq:
|
857
|
-
# conjugate_transpose -> NMatrix
|
858
|
-
#
|
859
|
-
# Calculate the conjugate transpose of a matrix. If your dtype is already
|
860
|
-
# complex, this should only require one copy (for the transpose).
|
861
|
-
#
|
862
|
-
# * *Returns* :
|
863
|
-
# - The conjugate transpose of the matrix as a copy.
|
864
|
-
#
|
865
|
-
def conjugate_transpose
|
866
|
-
self.transpose.complex_conjugate!
|
456
|
+
NMatrix.new([n,m], kron_prod_array)
|
867
457
|
end
|
868
458
|
|
869
459
|
#
|
@@ -910,7 +500,9 @@ class NMatrix
|
|
910
500
|
##
|
911
501
|
# call-seq:
|
912
502
|
# sum() -> NMatrix
|
503
|
+
# cumsum() -> NMatrix
|
913
504
|
# sum(dimen) -> NMatrix
|
505
|
+
# cumsum(dimen) -> NMatrix
|
914
506
|
#
|
915
507
|
# Calculates the sum along the specified dimension.
|
916
508
|
#
|
@@ -920,7 +512,7 @@ class NMatrix
|
|
920
512
|
sum + sub_mat
|
921
513
|
end
|
922
514
|
end
|
923
|
-
|
515
|
+
alias :cumsum :sum
|
924
516
|
|
925
517
|
##
|
926
518
|
# call-seq:
|
@@ -1033,79 +625,79 @@ class NMatrix
|
|
1033
625
|
end.cast(self.stype, abs_dtype)
|
1034
626
|
end
|
1035
627
|
|
1036
|
-
#
|
1037
|
-
#
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
def asum incx=1, n=nil
|
1046
|
-
if self.shape == [1]
|
1047
|
-
return self[0].abs unless self.complex_dtype?
|
1048
|
-
return self[0].real.abs + self[0].imag.abs
|
1049
|
-
end
|
1050
|
-
return method_missing(:asum, incx, n) unless vector?
|
1051
|
-
NMatrix::BLAS::asum(self, incx, self.size / incx)
|
628
|
+
# Norm calculation methods
|
629
|
+
# Frobenius norm: the Euclidean norm of the matrix, treated as if it were a vector
|
630
|
+
def fro_matrix_norm
|
631
|
+
#float64 has to be used in any case, since nrm2 will not yield correct result for float32
|
632
|
+
self_cast = self.cast(:dtype => :float64)
|
633
|
+
|
634
|
+
column_vector = self_cast.reshape([self.size, 1])
|
635
|
+
|
636
|
+
return column_vector.nrm2
|
1052
637
|
end
|
1053
|
-
alias :absolute_sum :asum
|
1054
638
|
|
1055
|
-
#
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
def nrm2 incx=1, n=nil
|
1065
|
-
return method_missing(:nrm2, incx, n) unless vector?
|
1066
|
-
NMatrix::BLAS::nrm2(self, incx, self.size / incx)
|
639
|
+
# 2-norm: the largest/smallest singular value of the matrix
|
640
|
+
def two_matrix_norm minus = false
|
641
|
+
|
642
|
+
self_cast = self.cast(:dtype => :float64)
|
643
|
+
|
644
|
+
#TODO: confirm if this is the desired svd calculation
|
645
|
+
svd = self_cast.gesvd
|
646
|
+
return svd[1][0, 0] unless minus
|
647
|
+
return svd[1][svd[1].rows-1, svd[1].cols-1]
|
1067
648
|
end
|
1068
|
-
alias :norm2 :nrm2
|
1069
649
|
|
1070
|
-
#
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
#
|
1079
|
-
# This is a destructive method, modifying the source NMatrix. See also #scale.
|
1080
|
-
# Return the scaling result of the matrix. BLAS scal will be invoked if provided.
|
1081
|
-
|
1082
|
-
def scale!(alpha, incx=1, n=nil)
|
1083
|
-
raise(DataTypeError, "Incompatible data type for the scaling factor") unless
|
1084
|
-
NMatrix::upcast(self.dtype, NMatrix::min_dtype(alpha)) == self.dtype
|
1085
|
-
return NMatrix::BLAS::scal(alpha, self, incx, self.size / incx) if NMatrix::BLAS.method_defined? :scal
|
1086
|
-
self.each_stored_with_indices do |e, *i|
|
1087
|
-
self[*i] = e*alpha
|
650
|
+
# 1-norm: the maximum/minimum absolute column sum of the matrix
|
651
|
+
def one_matrix_norm minus = false
|
652
|
+
#TODO: change traversing method for sparse matrices
|
653
|
+
number_of_columns = self.cols
|
654
|
+
col_sums = []
|
655
|
+
|
656
|
+
number_of_columns.times do |i|
|
657
|
+
col_sums << self.col(i).inject(0) { |sum, number| sum += number.abs}
|
1088
658
|
end
|
659
|
+
|
660
|
+
return col_sums.max unless minus
|
661
|
+
return col_sums.min
|
662
|
+
end
|
663
|
+
|
664
|
+
# Infinity norm: the maximum/minimum absolute row sum of the matrix
|
665
|
+
def inf_matrix_norm minus = false
|
666
|
+
number_of_rows = self.rows
|
667
|
+
row_sums = []
|
668
|
+
|
669
|
+
number_of_rows.times do |i|
|
670
|
+
row_sums << self.row(i).inject(0) { |sum, number| sum += number.abs}
|
671
|
+
end
|
672
|
+
|
673
|
+
return row_sums.max unless minus
|
674
|
+
return row_sums.min
|
1089
675
|
end
|
1090
676
|
|
1091
677
|
#
|
1092
678
|
# call-seq:
|
1093
|
-
#
|
679
|
+
# positive_definite? -> boolean
|
1094
680
|
#
|
1095
|
-
#
|
1096
|
-
#
|
1097
|
-
#
|
1098
|
-
# -
|
1099
|
-
#
|
1100
|
-
#
|
1101
|
-
|
1102
|
-
def
|
1103
|
-
|
681
|
+
# A matrix is positive definite if it’s symmetric and all its eigenvalues are positive
|
682
|
+
#
|
683
|
+
# * *Returns* :
|
684
|
+
# - A boolean value telling if the NMatrix is positive definite or not.
|
685
|
+
# * *Raises* :
|
686
|
+
# - +ShapeError+ -> Must be used on square matrices.
|
687
|
+
#
|
688
|
+
def positive_definite?
|
689
|
+
raise(ShapeError, "positive definite calculated only for square matrices") unless
|
690
|
+
self.dim == 2 && self.shape[0] == self.shape[1]
|
691
|
+
cond = 0
|
692
|
+
while cond != self.cols
|
693
|
+
if self[0..cond, 0..cond].det <= 0
|
694
|
+
return false
|
695
|
+
end
|
696
|
+
cond += 1
|
697
|
+
end
|
698
|
+
true
|
1104
699
|
end
|
1105
700
|
|
1106
|
-
alias :permute_columns :laswp
|
1107
|
-
alias :permute_columns! :laswp!
|
1108
|
-
|
1109
701
|
protected
|
1110
702
|
# Define the element-wise operations for lists. Note that the __list_map_merged_stored__ iterator returns a Ruby Object
|
1111
703
|
# matrix, which we then cast back to the appropriate type. If you don't want that, you can redefine these functions in
|
@@ -1321,3 +913,9 @@ protected
|
|
1321
913
|
end
|
1322
914
|
end
|
1323
915
|
end
|
916
|
+
|
917
|
+
if jruby?
|
918
|
+
require_relative "./jruby/math.rb"
|
919
|
+
else
|
920
|
+
require_relative "./cruby/math.rb"
|
921
|
+
end
|