nmatrix 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # adjugate! -> NMatrix
116
+ # exact_inverse! -> NMatrix
118
117
  #
119
- # Calculate the adjugate of the matrix (in-place).
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
- # - +StorageTypeError+ -> only implemented on dense matrices.
124
- # - +ShapeError+ -> matrix must be square.
125
- # - +DataTypeError+ -> cannot calculate adjugate of an integer matrix in-place.
126
- #
127
- def adjugate!
128
- raise(StorageTypeError, "adjugate only works on dense matrices currently") unless self.dense?
129
- raise(ShapeError, "Cannot calculate adjugate of a non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
130
- raise(DataTypeError, "Cannot calculate adjugate of an integer matrix in-place") if self.integer_dtype?
131
- d = self.det
132
- self.invert!
133
- self.map! { |e| e * d }
134
- self
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
- # adjugate -> NMatrix
139
+ # exact_inverse -> NMatrix
141
140
  #
142
- # Make a copy of the matrix and calculate the adjugate 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 adjugate
155
- raise(StorageTypeError, "adjugate only works on dense matrices currently") unless self.dense?
156
- raise(ShapeError, "Cannot calculate adjugate of a non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
157
- d = self.det
158
- mat = self.invert
159
- mat.map! { |e| e * d }
160
- mat
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 :adjoint :adjugate
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
- # unmqr(tau) -> NMatrix
273
- # unmqr(tau, side, transpose, c) -> NMatrix
170
+ # pinv -> NMatrix
274
171
  #
275
- # Returns the product Q * c or c * Q after a call to geqrf! used in QR factorization.
276
- # +c+ is overwritten with the elements of the result NMatrix if it is supplied. Q is the orthogonal matrix
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
- # == Arguments
175
+ # This function requires the nmatrix-atlas gem installed.
282
176
  #
283
- # * +tau+ - vector containing scalar factors of elementary reflectors
284
- # * +side+ - direction of multiplication [:left, :right]
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
- # - +StorageTypeError+ -> LAPACK functions only work on dense matrices.
295
- # - +TypeError+ -> Works only on floating point matrices, use unmqr for complex types
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
- def unmqr(tau, side=:left, transpose=false, c=nil)
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
- # call-seq:
305
- # potrf!(upper_or_lower) -> NMatrix
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
- # Cholesky factorization of a symmetric positive-definite matrix -- or, if complex,
308
- # a Hermitian positive-definite matrix +A+.
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
- # This functions requires either the nmatrix-atlas or nmatrix-lapacke gem
317
- # installed.
197
+ # == References
318
198
  #
319
- # * *Returns* :
320
- # the triangular portion specified by the parameter
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 potrf!(which)
327
- # The real implementation is in the plugin files.
328
- raise(NotImplementedError, "potrf! requires either the nmatrix-atlas or nmatrix-lapacke gem")
329
- end
330
-
331
- def potrf_upper!
332
- potrf! :upper
333
- end
334
-
335
- def potrf_lower!
336
- potrf! :lower
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
- # factorize_cholesky -> [upper NMatrix, lower NMatrix]
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
- # call-seq:
360
- # factorize_lu -> ...
236
+ # Calculate the adjugate of the matrix (in-place).
237
+ # Only works on dense matrices.
361
238
  #
362
- # LU factorization of a matrix. Optionally return the permutation matrix.
363
- # Note that computing the permutation matrix will introduce a slight memory
364
- # and time overhead.
365
- #
366
- # == Arguments
367
- #
368
- # +with_permutation_matrix+ - If set to *true* will return the permutation
369
- # matrix alongwith the LU factorization as a second return value.
370
- #
371
- def factorize_lu with_permutation_matrix=nil
372
- raise(NotImplementedError, "only implemented for dense storage") unless self.stype == :dense
373
- raise(NotImplementedError, "matrix is not 2-dimensional") unless self.dimensions == 2
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
- # factorize_qr -> [Q,R]
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
- # Only works for dense matrices.
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
- # - Array containing Q and R matrices
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 for desnse storage.
397
- # - +ShapeError+ -> Input must be a 2-dimensional matrix to have a QR decomposition.
398
- #
399
- def factorize_qr
400
- raise(NotImplementedError, "only implemented for dense storage") unless self.stype == :dense
401
- raise(ShapeError, "Input must be a 2-dimensional matrix to have a QR decomposition") unless self.dim == 2
402
-
403
- rows, columns = self.shape
404
- r = self.clone
405
- tau = r.geqrf!
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
- # det -> determinant
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
- # There are probably also more efficient ways to calculate the determinant.
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
- # For smaller matrices, you may be able to use +#det_exact+.
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
- # This function is guaranteed to return the same type of data in the matrix
685
- # upon which it is called.
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
- # Integer matrices are converted to floating point matrices for the purposes of
688
- # performing the calculation, as xGETRF can't work on integer matrices.
318
+ # Tested mainly with dense matrices. Further checks and modifications might
319
+ # be necessary for sparse matrices.
689
320
  #
690
321
  # * *Returns* :
691
- # - The determinant of the matrix. It's the same type as the matrix's dtype.
322
+ # - The selected norm of the matrix.
692
323
  # * *Raises* :
693
- # - +ShapeError+ -> Must be used on square matrices.
694
- #
695
- def det
696
- raise(ShapeError, "determinant can be calculated only for square matrices") unless self.dim == 2 && self.shape[0] == self.shape[1]
697
-
698
- # Cast to a dtype for which getrf is implemented
699
- new_dtype = self.integer_dtype? ? :float64 : self.dtype
700
- copy = self.cast(:dense, new_dtype)
701
-
702
- # Need to know the number of permutations. We'll add up the diagonals of
703
- # the factorized matrix.
704
- pivot = copy.getrf!
705
-
706
- num_perm = 0 #number of permutations
707
- pivot.each_with_index do |swap, i|
708
- #pivot indexes rows starting from 1, instead of 0, so need to subtract 1 here
709
- num_perm += 1 if swap-1 != i
710
- end
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
- # call-seq:
1038
- # absolute_sum -> Numeric
1039
- #
1040
- # == Arguments
1041
- # - +incx+ -> the skip size (defaults to 1, no skip)
1042
- # - +n+ -> the number of elements to include
1043
- #
1044
- # Return the sum of the contents of the vector. This is the BLAS asum routine.
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
- # call-seq:
1057
- # norm2 -> Numeric
1058
- #
1059
- # == Arguments
1060
- # - +incx+ -> the skip size (defaults to 1, no skip)
1061
- # - +n+ -> the number of elements to include
1062
- #
1063
- # Return the 2-norm of the vector. This is the BLAS nrm2 routine.
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
- # call-seq:
1072
- # scale! -> NMatrix
1073
- #
1074
- # == Arguments
1075
- # - +alpha+ -> Scalar value used in the operation.
1076
- # - +inc+ -> Increment used in the scaling function. Should generally be 1.
1077
- # - +n+ -> Number of elements of +vector+.
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
- # scale -> NMatrix
679
+ # positive_definite? -> boolean
1094
680
  #
1095
- # == Arguments
1096
- # - +alpha+ -> Scalar value used in the operation.
1097
- # - +inc+ -> Increment used in the scaling function. Should generally be 1.
1098
- # - +n+ -> Number of elements of +vector+.
1099
- #
1100
- # Return the scaling result of the matrix. BLAS scal will be invoked if provided.
1101
-
1102
- def scale(alpha, incx=1, n=nil)
1103
- return self.clone.scale!(alpha, incx, n)
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