nmatrix 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|