nmatrix 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/ext/nmatrix/data/complex.h +20 -55
  3. data/ext/nmatrix/data/data.cpp +11 -44
  4. data/ext/nmatrix/data/data.h +174 -311
  5. data/ext/nmatrix/data/meta.h +1 -7
  6. data/ext/nmatrix/data/ruby_object.h +3 -85
  7. data/ext/nmatrix/extconf.rb +2 -73
  8. data/ext/nmatrix/math.cpp +170 -813
  9. data/ext/nmatrix/math/asum.h +2 -25
  10. data/ext/nmatrix/math/{inc.h → cblas_enums.h} +11 -22
  11. data/ext/nmatrix/math/cblas_templates_core.h +507 -0
  12. data/ext/nmatrix/math/gemm.h +2 -32
  13. data/ext/nmatrix/math/gemv.h +1 -35
  14. data/ext/nmatrix/math/getrf.h +21 -6
  15. data/ext/nmatrix/math/getrs.h +0 -8
  16. data/ext/nmatrix/math/imax.h +0 -22
  17. data/ext/nmatrix/math/long_dtype.h +0 -3
  18. data/ext/nmatrix/math/math.h +11 -337
  19. data/ext/nmatrix/math/nrm2.h +2 -23
  20. data/ext/nmatrix/math/rot.h +1 -25
  21. data/ext/nmatrix/math/rotg.h +4 -13
  22. data/ext/nmatrix/math/scal.h +0 -22
  23. data/ext/nmatrix/math/trsm.h +0 -55
  24. data/ext/nmatrix/math/util.h +148 -0
  25. data/ext/nmatrix/nmatrix.cpp +0 -14
  26. data/ext/nmatrix/nmatrix.h +92 -84
  27. data/ext/nmatrix/ruby_constants.cpp +0 -2
  28. data/ext/nmatrix/ruby_constants.h +0 -2
  29. data/ext/nmatrix/ruby_nmatrix.c +86 -45
  30. data/ext/nmatrix/storage/dense/dense.cpp +1 -7
  31. data/ext/nmatrix/storage/storage.h +0 -1
  32. data/ext/nmatrix/ttable_helper.rb +0 -6
  33. data/ext/nmatrix/util/io.cpp +1 -1
  34. data/lib/nmatrix.rb +1 -19
  35. data/lib/nmatrix/blas.rb +33 -11
  36. data/lib/nmatrix/io/market.rb +3 -3
  37. data/lib/nmatrix/lapack_core.rb +181 -0
  38. data/lib/nmatrix/lapack_plugin.rb +44 -0
  39. data/lib/nmatrix/math.rb +382 -131
  40. data/lib/nmatrix/monkeys.rb +2 -3
  41. data/lib/nmatrix/nmatrix.rb +166 -13
  42. data/lib/nmatrix/shortcuts.rb +72 -7
  43. data/lib/nmatrix/version.rb +2 -2
  44. data/spec/00_nmatrix_spec.rb +154 -5
  45. data/spec/02_slice_spec.rb +2 -6
  46. data/spec/03_nmatrix_monkeys_spec.rb +7 -1
  47. data/spec/blas_spec.rb +60 -33
  48. data/spec/homogeneous_spec.rb +10 -10
  49. data/spec/lapack_core_spec.rb +482 -0
  50. data/spec/math_spec.rb +436 -52
  51. data/spec/shortcuts_spec.rb +28 -4
  52. data/spec/spec_helper.rb +14 -2
  53. data/spec/utm5940.mtx +83844 -0
  54. metadata +49 -76
  55. data/.gitignore +0 -27
  56. data/.rspec +0 -2
  57. data/.travis.yml +0 -15
  58. data/CONTRIBUTING.md +0 -82
  59. data/Gemfile +0 -2
  60. data/History.txt +0 -677
  61. data/LICENSE.txt +0 -23
  62. data/Manifest.txt +0 -92
  63. data/README.rdoc +0 -150
  64. data/Rakefile +0 -216
  65. data/ext/nmatrix/data/rational.h +0 -440
  66. data/ext/nmatrix/math/geev.h +0 -82
  67. data/ext/nmatrix/math/ger.h +0 -96
  68. data/ext/nmatrix/math/gesdd.h +0 -80
  69. data/ext/nmatrix/math/gesvd.h +0 -78
  70. data/ext/nmatrix/math/getf2.h +0 -86
  71. data/ext/nmatrix/math/getri.h +0 -108
  72. data/ext/nmatrix/math/potrs.h +0 -129
  73. data/ext/nmatrix/math/swap.h +0 -52
  74. data/lib/nmatrix/lapack.rb +0 -240
  75. data/nmatrix.gemspec +0 -55
  76. data/scripts/mac-brew-gcc.sh +0 -50
  77. data/scripts/mac-mavericks-brew-gcc.sh +0 -22
  78. data/spec/lapack_spec.rb +0 -459
@@ -0,0 +1,181 @@
1
+ #--
2
+ # = NMatrix
3
+ #
4
+ # A linear algebra library for scientific computation in Ruby.
5
+ # NMatrix is part of SciRuby.
6
+ #
7
+ # NMatrix was originally inspired by and derived from NArray, by
8
+ # Masahiro Tanaka: http://narray.rubyforge.org
9
+ #
10
+ # == Copyright Information
11
+ #
12
+ # SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
14
+ #
15
+ # Please see LICENSE.txt for additional copyright notices.
16
+ #
17
+ # == Contributing
18
+ #
19
+ # By contributing source code to SciRuby, you agree to be bound by
20
+ # our Contributor Agreement:
21
+ #
22
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
23
+ #
24
+ # == lapack_core.rb
25
+ #
26
+ # This file contains friendlier interfaces to LAPACK functions
27
+ # implemented in C.
28
+ # This file is only for functions available with the core nmatrix gem
29
+ # (no external libraries needed).
30
+ #
31
+ # Note: most of these functions are borrowed from ATLAS, which is available under a BSD-
32
+ # style license.
33
+ #++
34
+
35
+ class NMatrix
36
+
37
+ module LAPACK
38
+
39
+ #Add functions from C extension to main LAPACK module
40
+ class << self
41
+ NMatrix::Internal::LAPACK.singleton_methods.each do |m|
42
+ define_method m, NMatrix::Internal::LAPACK.method(m).to_proc
43
+ end
44
+ end
45
+
46
+ class << self
47
+ # Solve the matrix equation AX = B, where A is a symmetric (or Hermitian)
48
+ # positive-definite matrix. If A is a nxn matrix, B must be mxn.
49
+ # Depending on the value of uplo, only the upper or lower half of +a+
50
+ # is read.
51
+ # This uses the Cholesky decomposition so it should be faster than
52
+ # the generic NMatrix#solve method.
53
+ # Doesn't modify inputs.
54
+ # Requires either the nmatrix-atlas or nmatrix-lapacke gem.
55
+ # * *Arguments* :
56
+ # - +uplo+ -> Either +:upper+ or +:lower+. Specifies which half of +a+ to read.
57
+ # - +a+ -> The matrix A.
58
+ # - +b+ -> The right-hand side B.
59
+ # * *Returns* :
60
+ # - The solution X
61
+ def posv(uplo, a, b)
62
+ raise(NotImplementedError, "Either the nmatrix-atlas or nmatrix-lapacke gem must be installed to use posv")
63
+ end
64
+
65
+ # laswp(matrix, ipiv) -> NMatrix
66
+ #
67
+ # Permute the columns of a matrix (in-place) according to the Array +ipiv+.
68
+ #
69
+ def laswp(matrix, ipiv)
70
+ raise(ArgumentError, "expected NMatrix for argument 0") unless matrix.is_a?(NMatrix)
71
+ raise(StorageTypeError, "LAPACK functions only work on :dense NMatrix instances") unless matrix.stype == :dense
72
+ raise(ArgumentError, "expected Array ipiv to have no more entries than NMatrix a has columns") if ipiv.size > matrix.shape[1]
73
+
74
+ clapack_laswp(matrix.shape[0], matrix, matrix.shape[1], 0, ipiv.size-1, ipiv, 1)
75
+ end
76
+
77
+ def alloc_svd_result(matrix)
78
+ [
79
+ NMatrix.new(matrix.shape[0], dtype: matrix.dtype),
80
+ NMatrix.new([[matrix.shape[0],matrix.shape[1]].min,1], dtype: matrix.abs_dtype),
81
+ NMatrix.new(matrix.shape[1], dtype: matrix.dtype)
82
+ ]
83
+ end
84
+
85
+
86
+ #
87
+ # call-seq:
88
+ # gesvd(matrix) -> [u, sigma, v_transpose]
89
+ # gesvd(matrix) -> [u, sigma, v_conjugate_transpose] # complex
90
+ #
91
+ # Compute the singular value decomposition of a matrix using LAPACK's GESVD function.
92
+ #
93
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
94
+ # requires.
95
+ #
96
+ # Requires either the nmatrix-lapacke or nmatrix-atlas gem.
97
+ #
98
+ def gesvd(matrix, workspace_size=1)
99
+ raise(NotImplementedError,"gesvd requires either the nmatrix-atlas or nmatrix-lapacke gem")
100
+ end
101
+
102
+ #
103
+ # call-seq:
104
+ # gesdd(matrix) -> [u, sigma, v_transpose]
105
+ # gesdd(matrix) -> [u, sigma, v_conjugate_transpose] # complex
106
+ #
107
+ # Compute the singular value decomposition of a matrix using LAPACK's GESDD function. This uses a divide-and-conquer
108
+ # strategy. See also #gesvd.
109
+ #
110
+ # Optionally accepts a +workspace_size+ parameter, which will be honored only if it is larger than what LAPACK
111
+ # requires.
112
+ #
113
+ # Requires either the nmatrix-lapacke or nmatrix-atlas gem.
114
+ #
115
+ def gesdd(matrix, workspace_size=nil)
116
+ raise(NotImplementedError,"gesvd requires either the nmatrix-atlas or nmatrix-lapacke gem")
117
+ end
118
+
119
+ #
120
+ # call-seq:
121
+ # geev(matrix) -> [eigenvalues, left_eigenvectors, right_eigenvectors]
122
+ # geev(matrix, :left) -> [eigenvalues, left_eigenvectors]
123
+ # geev(matrix, :right) -> [eigenvalues, right_eigenvectors]
124
+ #
125
+ # Perform eigenvalue decomposition on a matrix using LAPACK's xGEEV function.
126
+ #
127
+ # +eigenvalues+ is a n-by-1 NMatrix containing the eigenvalues.
128
+ #
129
+ # +right_eigenvalues+ is a n-by-n matrix such that its j'th column
130
+ # contains the (right) eigenvalue of +matrix+ corresponding
131
+ # to the j'th eigenvalue.
132
+ # This means that +matrix+ = RDR^(-1),
133
+ # where R is +right_eigenvalues+ and D is the diagonal matrix formed
134
+ # from +eigenvalues+.
135
+ #
136
+ # +left_eigenvalues+ is n-by-n and its columns are the left
137
+ # eigenvalues of +matrix+, using the {definition of left eigenvalue
138
+ # from LAPACK}[https://software.intel.com/en-us/node/521147].
139
+ #
140
+ # For real dtypes, +eigenvalues+ and the eigenvector matrices
141
+ # will be complex if and only if +matrix+ has complex eigenvalues.
142
+ #
143
+ # Only available if nmatrix-lapack or nmatrix-atlas is installed.
144
+ #
145
+ def geev(matrix, which=:both)
146
+ raise(NotImplementedError, "geev requires either the nmatrix-atlas or nmatrix-lapack gem")
147
+ end
148
+
149
+ # The following are functions that used to be implemented in C, but
150
+ # now require nmatrix-atlas to run properly, so we can just
151
+ # implemented their stubs in Ruby.
152
+ def lapack_gesvd(jobu, jobvt, m, n, a, lda, s, u, ldu, vt, ldvt, lwork)
153
+ raise(NotImplementedError,"lapack_gesvd requires the nmatrix-atlas gem")
154
+ end
155
+
156
+ def lapack_gesdd(jobz, m, n, a, lda, s, u, ldu, vt, ldvt, lwork)
157
+ raise(NotImplementedError,"lapack_gesdd requires the nmatrix-atlas gem")
158
+ end
159
+
160
+ def lapack_geev(jobvl, jobvr, n, a, lda, w, wi, vl, ldvl, vr, ldvr, lwork)
161
+ raise(NotImplementedError,"lapack_geev requires the nmatrix-atlas gem")
162
+ end
163
+
164
+ def clapack_potrf(order, uplo, n, a, lda)
165
+ raise(NotImplementedError,"clapack_potrf requires the nmatrix-atlas gem")
166
+ end
167
+
168
+ def clapack_potri(order, uplo, n, a, lda)
169
+ raise(NotImplementedError,"clapack_potri requires the nmatrix-atlas gem")
170
+ end
171
+
172
+ def clapack_potrs(order, uplo, n, nrhs, a, lda, b, ldb)
173
+ raise(NotImplementedError,"clapack_potrs requires the nmatrix-atlas gem")
174
+ end
175
+
176
+ def clapack_getri(order, n, a, lda, ipiv)
177
+ raise(NotImplementedError,"clapack_getri requires the nmatrix-atlas gem")
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,44 @@
1
+ #--
2
+ # = NMatrix
3
+ #
4
+ # A linear algebra library for scientific computation in Ruby.
5
+ # NMatrix is part of SciRuby.
6
+ #
7
+ # NMatrix was originally inspired by and derived from NArray, by
8
+ # Masahiro Tanaka: http://narray.rubyforge.org
9
+ #
10
+ # == Copyright Information
11
+ #
12
+ # SciRuby is Copyright (c) 2010 - 2014, Ruby Science Foundation
13
+ # NMatrix is Copyright (c) 2012 - 2014, John Woods and the Ruby Science Foundation
14
+ #
15
+ # Please see LICENSE.txt for additional copyright notices.
16
+ #
17
+ # == Contributing
18
+ #
19
+ # By contributing source code to SciRuby, you agree to be bound by
20
+ # our Contributor Agreement:
21
+ #
22
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
23
+ #
24
+ # == lapack_plugin.rb
25
+ #
26
+ # This file `require`s either nmatrix-atlas or nmatrix-lapacke depending on which
27
+ # is available.
28
+ #
29
+ # The idea is that if a developer wants to use a LAPACK feature which is provided
30
+ # by both of these gems (e.g. NMatrix#potrf! or NMatrix::LAPACK.geev),
31
+ # but doesn't care which one is installed, they can
32
+ # just `require 'nmatrix/lapack_plugin'` rather than having to choose between
33
+ # `require 'nmatrix/lapacke'` or `require 'nmatrix/lapacke'`
34
+ #++
35
+
36
+ begin
37
+ require 'nmatrix/atlas'
38
+ rescue LoadError
39
+ begin
40
+ require 'nmatrix/lapacke'
41
+ rescue LoadError
42
+ raise(LoadError,"Either nmatrix-atlas or nmatrix-lapacke must be installed")
43
+ end
44
+ end
@@ -36,6 +36,31 @@ class NMatrix
36
36
  :asinh, :atanh, :exp, :log2, :log10, :sqrt, :cbrt, :erf, :erfc, :gamma, :-@]
37
37
  end
38
38
 
39
+ # Methods for generating permutation matrix from LU factorization results.
40
+ module FactorizeLUMethods
41
+ class << self
42
+ def permutation_matrix_from(pivot_array)
43
+ perm_arry = permutation_array_for(pivot_array)
44
+ n = NMatrix.zeros(perm_arry.size, dtype: :byte)
45
+
46
+ perm_arry.each_with_index { |e, i| n[e,i] = 1 }
47
+
48
+ n
49
+ end
50
+
51
+ def permutation_array_for(pivot_array)
52
+ perm_arry = Array.new(pivot_array.size) { |i| i }
53
+ perm_arry.each_index do |i|
54
+ #the pivot indices returned by LAPACK getrf are indexed starting
55
+ #from 1, so we need to subtract 1 here
56
+ perm_arry[i], perm_arry[pivot_array[i]-1] = perm_arry[pivot_array[i]-1], perm_arry[i]
57
+ end
58
+
59
+ perm_arry
60
+ end
61
+ end
62
+ end
63
+
39
64
  #
40
65
  # call-seq:
41
66
  # invert! -> NMatrix
@@ -44,23 +69,18 @@ class NMatrix
44
69
  # Only works on dense matrices. Alternatively uses in-place Gauss-Jordan
45
70
  # elimination.
46
71
  #
72
+ # * *Raises* :
73
+ # - +StorageTypeError+ -> only implemented on dense matrices.
74
+ # - +ShapeError+ -> matrix must be square.
75
+ # - +DataTypeError+ -> cannot invert an integer matrix in-place.
76
+ #
47
77
  def invert!
48
- if NMatrix.has_clapack?
49
- # Get the pivot array; factor the matrix
50
- pivot = self.getrf!
51
-
52
- # Now calculate the inverse using the pivot array
53
- NMatrix::LAPACK::clapack_getri(:row, self.shape[1], self, self.shape[1], pivot)
78
+ raise(StorageTypeError, "invert only works on dense matrices currently") unless self.dense?
79
+ raise(ShapeError, "Cannot invert non-square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
80
+ raise(DataTypeError, "Cannot invert an integer matrix in-place") if self.integer_dtype?
54
81
 
55
- self
56
- else
57
- if self.integer_dtype?
58
- __inverse__(self.cast(dtype: :rational128), true)
59
- else
60
- dtype = self.dtype
61
- __inverse__(self, true)
62
- end
63
- end
82
+ #No internal implementation of getri, so use this other function
83
+ __inverse__(self, true)
64
84
  end
65
85
 
66
86
  #
@@ -70,45 +90,42 @@ class NMatrix
70
90
  # Make a copy of the matrix, then invert using Gauss-Jordan elimination.
71
91
  # Works without LAPACK.
72
92
  #
73
- #
74
93
  # * *Returns* :
75
- # - A dense NMatrix.
76
- #
77
- def invert lda=nil, ldb=nil
78
- if lda.nil? and ldb.nil?
79
- if NMatrix.has_clapack?
80
- begin
81
- self.cast(:dense, self.dtype).invert! # call CLAPACK version
82
- rescue NotImplementedError # probably a rational matrix
83
- inverse = self.clone
84
- __inverse__(inverse, false)
85
- end
86
- elsif self.integer_dtype? # FIXME: This check is probably too slow.
87
- rational_self = self.cast(dtype: :rational128)
88
- inverse = rational_self.clone
89
- rational_self.__inverse__(inverse, false)
90
- else
91
- inverse = self.clone
92
- __inverse__(inverse, false)
93
- end
94
+ # - A dense NMatrix. Will be the same type as the input NMatrix,
95
+ # except if the input is an integral dtype, in which case it will be a
96
+ # :float64 NMatrix.
97
+ #
98
+ # * *Raises* :
99
+ # - +StorageTypeError+ -> only implemented on dense matrices.
100
+ # - +ShapeError+ -> matrix must be square.
101
+ #
102
+ def invert
103
+ #write this in terms of invert! so plugins will only have to overwrite
104
+ #invert! and not invert
105
+ if self.integer_dtype?
106
+ cloned = self.cast(dtype: :float64)
107
+ cloned.invert!
94
108
  else
95
- inverse = self.clone_structure
96
- if self.integer_dtype?
97
- __inverse_exact__(inverse.cast(dtype: :rational128), lda, ldb)
98
- else
99
- dtype = self.dtype
100
- __inverse_exact__(inverse, lda, ldb)
101
- end
109
+ cloned = self.clone
110
+ cloned.invert!
102
111
  end
103
112
  end
104
113
  alias :inverse :invert
105
114
 
106
115
  #
107
116
  # call-seq:
108
- # getrf! -> NMatrix
117
+ # getrf! -> Array
109
118
  #
110
119
  # LU factorization of a general M-by-N matrix +A+ using partial pivoting with
111
- # row interchanges. Only works in dense matrices.
120
+ # row interchanges. The LU factorization is A = PLU, where P is a row permutation
121
+ # matrix, L is a lower triangular matrix with unit diagonals, and U is an upper
122
+ # triangular matrix (note that this convention is different from the
123
+ # clapack_getrf behavior, but matches the standard LAPACK getrf).
124
+ # +A+ is overwritten with the elements of L and U (the unit
125
+ # diagonal elements of L are not saved). P is not returned directly and must be
126
+ # constructed from the pivot array ipiv. The row indices in ipiv are indexed
127
+ # starting from 1.
128
+ # Only works for dense matrices.
112
129
  #
113
130
  # * *Returns* :
114
131
  # - The IPIV vector. The L and U matrices are stored in A.
@@ -117,44 +134,51 @@ class NMatrix
117
134
  #
118
135
  def getrf!
119
136
  raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
120
- NMatrix::LAPACK::clapack_getrf(:row, self.shape[0], self.shape[1], self, self.shape[1])
121
- end
122
-
123
137
 
124
- #
125
- # call-seq:
126
- # getrf -> NMatrix
127
- #
128
- # In-place version of #getrf!. Returns the new matrix, which contains L and U matrices.
129
- #
130
- # * *Raises* :
131
- # - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
132
- #
133
- def getrf
134
- a = self.clone
135
- a.getrf!
136
- return a
138
+ #For row-major matrices, clapack_getrf uses a different convention than
139
+ #described above (U has unit diagonal elements instead of L and columns
140
+ #are interchanged rather than rows). For column-major matrices, clapack
141
+ #uses the stanard conventions. So we just transpose the matrix before
142
+ #and after calling clapack_getrf.
143
+ #Unfortunately, this is not a very good way, uses a lot of memory.
144
+ temp = self.transpose
145
+ ipiv = NMatrix::LAPACK::clapack_getrf(:col, self.shape[0], self.shape[1], temp, self.shape[0])
146
+ temp = temp.transpose
147
+ self[0...self.shape[0], 0...self.shape[1]] = temp
148
+
149
+ #for some reason, in clapack_getrf, the indices in ipiv start from 0
150
+ #instead of 1 as in LAPACK.
151
+ ipiv.each_index { |i| ipiv[i]+=1 }
152
+
153
+ return ipiv
137
154
  end
138
155
 
139
-
140
156
  #
141
157
  # call-seq:
142
158
  # potrf!(upper_or_lower) -> NMatrix
143
159
  #
144
160
  # Cholesky factorization of a symmetric positive-definite matrix -- or, if complex,
145
- # a Hermitian positive-definite matrix +A+. This uses the ATLAS function clapack_potrf,
146
- # so the result will be written in either the upper or lower triangular portion of the
147
- # matrix upon which it is called.
161
+ # a Hermitian positive-definite matrix +A+.
162
+ # The result will be written in either the upper or lower triangular portion of the
163
+ # matrix, depending on whether the argument is +:upper+ or +:lower+.
164
+ # Also the function only reads in the upper or lower part of the matrix,
165
+ # so it doesn't actually have to be symmetric/Hermitian.
166
+ # However, if the matrix (i.e. the symmetric matrix implied by the lower/upper
167
+ # half) is not positive-definite, the function will return nonsense.
168
+ #
169
+ # This functions requires either the nmatrix-atlas or nmatrix-lapacke gem
170
+ # installed.
148
171
  #
149
172
  # * *Returns* :
150
173
  # the triangular portion specified by the parameter
151
174
  # * *Raises* :
152
175
  # - +StorageTypeError+ -> ATLAS functions only work on dense matrices.
176
+ # - +ShapeError+ -> Must be square.
177
+ # - +NotImplementedError+ -> If called without nmatrix-atlas or nmatrix-lapacke gem
153
178
  #
154
179
  def potrf!(which)
155
- raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
156
- # FIXME: Surely there's an easy way to calculate one of these from the other. Do we really need to run twice?
157
- NMatrix::LAPACK::clapack_potrf(:row, which, self.shape[0], self, self.shape[1])
180
+ # The real implementation is in the plugin files.
181
+ raise(NotImplementedError, "potrf! requires either the nmatrix-atlas or nmatrix-lapacke gem")
158
182
  end
159
183
 
160
184
  def potrf_upper!
@@ -168,30 +192,102 @@ class NMatrix
168
192
 
169
193
  #
170
194
  # call-seq:
171
- # factorize_cholesky -> ...
195
+ # factorize_cholesky -> [upper NMatrix, lower NMatrix]
172
196
  #
173
- # Cholesky factorization of a matrix.
197
+ # Calculates the Cholesky factorization of a matrix and returns the
198
+ # upper and lower matrices such that A=LU and L=U*, where * is
199
+ # either the transpose or conjugate transpose.
200
+ #
201
+ # Unlike potrf!, this makes method requires that the original is matrix is
202
+ # symmetric or Hermitian. However, it is still your responsibility to make
203
+ # sure it is positive-definite.
174
204
  def factorize_cholesky
175
- [self.clone.potrf_upper!.triu!,
176
- self.clone.potrf_lower!.tril!]
205
+ raise "Matrix must be symmetric/Hermitian for Cholesky factorization" unless self.hermitian?
206
+ l = self.clone.potrf_lower!.tril!
207
+ u = l.conjugate_transpose
208
+ [u,l]
177
209
  end
178
210
 
179
211
  #
180
212
  # call-seq:
181
213
  # factorize_lu -> ...
182
214
  #
183
- # LU factorization of a matrix.
184
- #
185
- # FIXME: For some reason, getrf seems to require that the matrix be transposed first -- and then you have to transpose the
186
- # FIXME: result again. Ideally, this would be an in-place factorize instead, and would be called nm_factorize_lu_bang.
187
- #
188
- def factorize_lu
215
+ # LU factorization of a matrix. Optionally return the permutation matrix.
216
+ # Note that computing the permutation matrix will introduce a slight memory
217
+ # and time overhead.
218
+ #
219
+ # == Arguments
220
+ #
221
+ # +with_permutation_matrix+ - If set to *true* will return the permutation
222
+ # matrix alongwith the LU factorization as a second return value.
223
+ #
224
+ def factorize_lu with_permutation_matrix=nil
189
225
  raise(NotImplementedError, "only implemented for dense storage") unless self.stype == :dense
190
226
  raise(NotImplementedError, "matrix is not 2-dimensional") unless self.dimensions == 2
191
227
 
192
- t = self.transpose
193
- NMatrix::LAPACK::clapack_getrf(:row, t.shape[0], t.shape[1], t, t.shape[1])
194
- t.transpose
228
+ t = self.clone
229
+ pivot = t.getrf!
230
+ return t unless with_permutation_matrix
231
+
232
+ [t, FactorizeLUMethods.permutation_matrix_from(pivot)]
233
+ end
234
+
235
+ # Reduce self to upper hessenberg form using householder transforms.
236
+ #
237
+ # == References
238
+ #
239
+ # * http://en.wikipedia.org/wiki/Hessenberg_matrix
240
+ # * http://www.mymathlib.com/c_source/matrices/eigen/hessenberg_orthog.c
241
+ def hessenberg
242
+ clone.hessenberg!
243
+ end
244
+
245
+ # Destructive version of #hessenberg
246
+ def hessenberg!
247
+ raise ShapeError, "Trying to reduce non 2D matrix to hessenberg form" if
248
+ shape.size != 2
249
+ raise ShapeError, "Trying to reduce non-square matrix to hessenberg form" if
250
+ shape[0] != shape[1]
251
+ raise StorageTypeError, "Matrix must be dense" if stype != :dense
252
+ raise TypeError, "Works with float matrices only" unless
253
+ [:float64,:float32].include?(dtype)
254
+
255
+ __hessenberg__(self)
256
+ self
257
+ end
258
+
259
+ # Solve the matrix equation AX = B, where A is +self+, B is the first
260
+ # argument, and X is returned. A must be a nxn square matrix, while B must be
261
+ # nxm. Only works with dense
262
+ # matrices and non-integer, non-object data types.
263
+ #
264
+ # == Usage
265
+ #
266
+ # a = NMatrix.new [2,2], [3,1,1,2], dtype: dtype
267
+ # b = NMatrix.new [2,1], [9,8], dtype: dtype
268
+ # a.solve(b)
269
+ def solve b
270
+ raise(ShapeError, "Must be called on square matrix") unless self.dim == 2 && self.shape[0] == self.shape[1]
271
+ raise(ShapeError, "number of rows of b must equal number of cols of self") if
272
+ self.shape[1] != b.shape[0]
273
+ raise ArgumentError, "only works with dense matrices" if self.stype != :dense
274
+ raise ArgumentError, "only works for non-integer, non-object dtypes" if
275
+ integer_dtype? or object_dtype? or b.integer_dtype? or b.object_dtype?
276
+
277
+ x = b.clone
278
+ clone = self.clone
279
+ n = self.shape[0]
280
+ nrhs = b.shape[1]
281
+
282
+ ipiv = NMatrix::LAPACK.clapack_getrf(:row, n, n, clone, n)
283
+ # When we call clapack_getrs with :row, actually only the first matrix
284
+ # (i.e. clone) is interpreted as row-major, while the other matrix (x)
285
+ # is interpreted as column-major. See here: http://math-atlas.sourceforge.net/faq.html#RowSolve
286
+ # So we must transpose x before and after
287
+ # calling it.
288
+ x = x.transpose
289
+ NMatrix::LAPACK.clapack_getrs(:row, :no_transpose, n, nrhs, clone, n, ipiv, x, n)
290
+ x.transpose
195
291
  end
196
292
 
197
293
  #
@@ -254,24 +350,80 @@ class NMatrix
254
350
  def gesdd(workspace_size=nil)
255
351
  self.clone.gesdd!(workspace_size)
256
352
  end
353
+
257
354
  #
258
355
  # call-seq:
259
356
  # laswp!(ary) -> NMatrix
260
357
  #
261
- # In-place permute the columns of a dense matrix using LASWP according to the order given in an Array +ary+.
262
- # Not yet implemented for yale or list.
263
- def laswp!(ary)
264
- NMatrix::LAPACK::laswp(self, ary)
358
+ # In-place permute the columns of a dense matrix using LASWP according to the order given as an array +ary+.
359
+ #
360
+ # If +:convention+ is +:lapack+, then +ary+ represents a sequence of pair-wise permutations which are
361
+ # performed successively. That is, the i'th entry of +ary+ is the index of the column to swap
362
+ # the i'th column with, having already applied all earlier swaps.
363
+ #
364
+ # If +:convention+ is +:intuitive+, then +ary+ represents the order of columns after the permutation.
365
+ # That is, the i'th entry of +ary+ is the index of the column that will be in position i after the
366
+ # reordering (Matlab-like behaviour). This is the default.
367
+ #
368
+ # Not yet implemented for yale or list.
369
+ #
370
+ # == Arguments
371
+ #
372
+ # * +ary+ - An Array specifying the order of the columns. See above for details.
373
+ #
374
+ # == Options
375
+ #
376
+ # * +:covention+ - Possible values are +:lapack+ and +:intuitive+. Default is +:intuitive+. See above for details.
377
+ #
378
+ def laswp!(ary, opts={})
379
+ raise(StorageTypeError, "ATLAS functions only work on dense matrices") unless self.dense?
380
+ opts = { convention: :intuitive }.merge(opts)
381
+
382
+ if opts[:convention] == :intuitive
383
+ if ary.length != ary.uniq.length
384
+ raise(ArgumentError, "No duplicated entries in the order array are allowed under convention :intuitive")
385
+ end
386
+ n = self.shape[1]
387
+ p = []
388
+ order = (0...n).to_a
389
+ 0.upto(n-2) do |i|
390
+ p[i] = order.index(ary[i])
391
+ order[i], order[p[i]] = order[p[i]], order[i]
392
+ end
393
+ p[n-1] = n-1
394
+ else
395
+ p = ary
396
+ end
397
+
398
+ NMatrix::LAPACK::laswp(self, p)
265
399
  end
266
400
 
267
401
  #
268
402
  # call-seq:
269
403
  # laswp(ary) -> NMatrix
270
404
  #
271
- # Permute the columns of a dense matrix using LASWP according to the order given in an Array +ary+.
272
- # Not yet implemented for yale or list.
273
- def laswp(ary)
274
- self.clone.laswp!(ary)
405
+ # Permute the columns of a dense matrix using LASWP according to the order given in an array +ary+.
406
+ #
407
+ # If +:convention+ is +:lapack+, then +ary+ represents a sequence of pair-wise permutations which are
408
+ # performed successively. That is, the i'th entry of +ary+ is the index of the column to swap
409
+ # the i'th column with, having already applied all earlier swaps. This is the default.
410
+ #
411
+ # If +:convention+ is +:intuitive+, then +ary+ represents the order of columns after the permutation.
412
+ # That is, the i'th entry of +ary+ is the index of the column that will be in position i after the
413
+ # reordering (Matlab-like behaviour).
414
+ #
415
+ # Not yet implemented for yale or list.
416
+ #
417
+ # == Arguments
418
+ #
419
+ # * +ary+ - An Array specifying the order of the columns. See above for details.
420
+ #
421
+ # == Options
422
+ #
423
+ # * +:covention+ - Possible values are +:lapack+ and +:intuitive+. Default is +:lapack+. See above for details.
424
+ #
425
+ def laswp(ary, opts={})
426
+ self.clone.laswp!(ary, opts)
275
427
  end
276
428
 
277
429
  #
@@ -279,7 +431,7 @@ class NMatrix
279
431
  # det -> determinant
280
432
  #
281
433
  # Calculate the determinant by way of LU decomposition. This is accomplished
282
- # using clapack_getrf, and then by summing the diagonal elements. There is a
434
+ # using clapack_getrf, and then by taking the product of the diagonal elements. There is a
283
435
  # risk of underflow/overflow.
284
436
  #
285
437
  # There are probably also more efficient ways to calculate the determinant.
@@ -290,35 +442,38 @@ class NMatrix
290
442
  #
291
443
  # This function is guaranteed to return the same type of data in the matrix
292
444
  # upon which it is called.
293
- # In other words, if you call it on a rational matrix, you'll get a rational
294
- # number back.
295
445
  #
296
- # Integer matrices are converted to rational matrices for the purposes of
446
+ # Integer matrices are converted to floating point matrices for the purposes of
297
447
  # performing the calculation, as xGETRF can't work on integer matrices.
298
448
  #
299
449
  # * *Returns* :
300
450
  # - The determinant of the matrix. It's the same type as the matrix's dtype.
301
451
  # * *Raises* :
302
- # - +NotImplementedError+ -> Must be used in 2D matrices.
452
+ # - +ShapeError+ -> Must be used on square matrices.
303
453
  #
304
454
  def det
305
- raise(NotImplementedError, "determinant can be calculated only for 2D matrices") unless self.dim == 2
455
+ raise(ShapeError, "determinant can be calculated only for square matrices") unless self.dim == 2 && self.shape[0] == self.shape[1]
306
456
 
307
457
  # Cast to a dtype for which getrf is implemented
308
- new_dtype = [:byte,:int8,:int16,:int32,:int64].include?(self.dtype) ? :rational128 : self.dtype
458
+ new_dtype = self.integer_dtype? ? :float64 : self.dtype
309
459
  copy = self.cast(:dense, new_dtype)
310
460
 
311
461
  # Need to know the number of permutations. We'll add up the diagonals of
312
462
  # the factorized matrix.
313
463
  pivot = copy.getrf!
314
464
 
315
- prod = pivot.size % 2 == 1 ? -1 : 1 # odd permutations => negative
465
+ num_perm = 0 #number of permutations
466
+ pivot.each_with_index do |swap, i|
467
+ #pivot indexes rows starting from 1, instead of 0, so need to subtract 1 here
468
+ num_perm += 1 if swap-1 != i
469
+ end
470
+ prod = num_perm % 2 == 1 ? -1 : 1 # odd permutations => negative
316
471
  [shape[0],shape[1]].min.times do |i|
317
472
  prod *= copy[i,i]
318
473
  end
319
474
 
320
475
  # Convert back to an integer if necessary
321
- new_dtype != self.dtype ? prod.to_i : prod
476
+ new_dtype != self.dtype ? prod.round : prod #prevent rounding errors
322
477
  end
323
478
 
324
479
  #
@@ -342,6 +497,120 @@ class NMatrix
342
497
  self.cast(new_stype, NMatrix::upcast(dtype, :complex64)).complex_conjugate!
343
498
  end
344
499
 
500
+ # Calculate the variance co-variance matrix
501
+ #
502
+ # == Options
503
+ #
504
+ # * +:for_sample_data+ - Default true. If set to false will consider the denominator for
505
+ # population data (i.e. N, as opposed to N-1 for sample data).
506
+ #
507
+ # == References
508
+ #
509
+ # * http://stattrek.com/matrix-algebra/covariance-matrix.aspx
510
+ def cov(opts={})
511
+ raise TypeError, "Only works for non-integer dtypes" if integer_dtype?
512
+ opts = {
513
+ for_sample_data: true
514
+ }.merge(opts)
515
+
516
+ denominator = opts[:for_sample_data] ? rows - 1 : rows
517
+ ones = NMatrix.ones [rows,1]
518
+ deviation_scores = self - ones.dot(ones.transpose).dot(self) / rows
519
+ deviation_scores.transpose.dot(deviation_scores) / denominator
520
+ end
521
+
522
+ # Calculate the correlation matrix.
523
+ def corr
524
+ raise NotImplementedError, "Does not work for complex dtypes" if complex_dtype?
525
+ standard_deviation = std
526
+ cov / (standard_deviation.transpose.dot(standard_deviation))
527
+ end
528
+
529
+ # Raise a square matrix to a power. Be careful of numeric overflows!
530
+ # In case *n* is 0, an identity matrix of the same dimension is returned. In case
531
+ # of negative *n*, the matrix is inverted and the absolute value of *n* taken
532
+ # for computing the power.
533
+ #
534
+ # == Arguments
535
+ #
536
+ # * +n+ - Integer to which self is to be raised.
537
+ #
538
+ # == References
539
+ #
540
+ # * R.G Dromey - How to Solve it by Computer. Link -
541
+ # 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
542
+ def pow n
543
+ raise ShapeError, "Only works with 2D square matrices." if
544
+ shape[0] != shape[1] or shape.size != 2
545
+ raise TypeError, "Only works with integer powers" unless n.is_a?(Integer)
546
+
547
+ sequence = (integer_dtype? ? self.cast(dtype: :int64) : self).clone
548
+ product = NMatrix.eye shape[0], dtype: sequence.dtype, stype: sequence.stype
549
+
550
+ if n == 0
551
+ return NMatrix.eye(shape, dtype: dtype, stype: stype)
552
+ elsif n == 1
553
+ return sequence
554
+ elsif n < 0
555
+ n = n.abs
556
+ sequence.invert!
557
+ product = NMatrix.eye shape[0], dtype: sequence.dtype, stype: sequence.stype
558
+ end
559
+
560
+ # Decompose n to reduce the number of multiplications.
561
+ while n > 0
562
+ product = product.dot(sequence) if n % 2 == 1
563
+ n = n / 2
564
+ sequence = sequence.dot(sequence)
565
+ end
566
+
567
+ product
568
+ end
569
+
570
+ # Compute the Kronecker product of +self+ and other NMatrix
571
+ #
572
+ # === Arguments
573
+ #
574
+ # * +mat+ - A 2D NMatrix object
575
+ #
576
+ # === Usage
577
+ #
578
+ # a = NMatrix.new([2,2],[1,2,
579
+ # 3,4])
580
+ # b = NMatrix.new([2,3],[1,1,1,
581
+ # 1,1,1], dtype: :float64)
582
+ # a.kron_prod(b) # => [ [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
583
+ # [1.0, 1.0, 1.0, 2.0, 2.0, 2.0]
584
+ # [3.0, 3.0, 3.0, 4.0, 4.0, 4.0]
585
+ # [3.0, 3.0, 3.0, 4.0, 4.0, 4.0] ]
586
+ #
587
+ def kron_prod(mat)
588
+ unless self.dimensions==2 and mat.dimensions==2
589
+ raise ShapeError, "Implemented for 2D NMatrix objects only."
590
+ end
591
+
592
+ # compute the shape [n,m] of the product matrix
593
+ n, m = self.shape[0]*mat.shape[0], self.shape[1]*mat.shape[1]
594
+ # compute the entries of the product matrix
595
+ kron_prod_array = []
596
+ if self.yale?
597
+ # +:yale+ requires to get the row by copy in order to apply +#transpose+ to it
598
+ self.each_row(getby=:copy) do |selfr|
599
+ mat.each_row do |matr|
600
+ kron_prod_array += (selfr.transpose.dot matr).to_flat_a
601
+ end
602
+ end
603
+ else
604
+ self.each_row do |selfr|
605
+ mat.each_row do |matr|
606
+ kron_prod_array += (selfr.transpose.dot matr).to_flat_a
607
+ end
608
+ end
609
+ end
610
+
611
+ NMatrix.new([n,m], kron_prod_array)
612
+ end
613
+
345
614
  #
346
615
  # call-seq:
347
616
  # conjugate_transpose -> NMatrix
@@ -356,27 +625,6 @@ class NMatrix
356
625
  self.transpose.complex_conjugate!
357
626
  end
358
627
 
359
- #
360
- # call-seq:
361
- # hermitian? -> Boolean
362
- #
363
- # A hermitian matrix is a complex square matrix that is equal to its
364
- # conjugate transpose. (http://en.wikipedia.org/wiki/Hermitian_matrix)
365
- #
366
- # * *Returns* :
367
- # - True if +self+ is a hermitian matrix, nil otherwise.
368
- #
369
- def hermitian?
370
- return false if self.dim != 2 or self.shape[0] != self.shape[1]
371
-
372
- if [:complex64, :complex128].include?(self.dtype)
373
- # TODO: Write much faster Hermitian test in C
374
- self.eql?(self.conjugate_transpose)
375
- else
376
- symmetric?
377
- end
378
- end
379
-
380
628
  #
381
629
  # call-seq:
382
630
  # trace -> Numeric
@@ -555,7 +803,10 @@ class NMatrix
555
803
  #
556
804
  # Return the sum of the contents of the vector. This is the BLAS asum routine.
557
805
  def asum incx=1, n=nil
558
- return self[0].abs if self.shape == [1]
806
+ if self.shape == [1]
807
+ return self[0].abs unless self.complex_dtype?
808
+ return self[0].real.abs + self[0].imag.abs
809
+ end
559
810
  return method_missing(:asum, incx, n) unless vector?
560
811
  NMatrix::BLAS::asum(self, incx, self.size / incx)
561
812
  end
@@ -608,7 +859,7 @@ protected
608
859
  # These don't actually take an argument -- they're called reverse-polish style on the matrix.
609
860
  # This group always gets casted to float64.
610
861
  [:log, :log2, :log10, :sqrt, :sin, :cos, :tan, :acos, :asin, :atan, :cosh, :sinh, :tanh, :acosh,
611
- :asinh, :atanh, :exp, :erf, :erfc, :gamma, :cbrt].each do |ewop|
862
+ :asinh, :atanh, :exp, :erf, :erfc, :gamma, :cbrt, :round].each do |ewop|
612
863
  define_method("__list_unary_#{ewop}__") do
613
864
  self.__list_map_stored__(nil) { |l| Math.send(ewop, l) }.cast(stype, NMatrix.upcast(dtype, :float64))
614
865
  end
@@ -648,31 +899,31 @@ protected
648
899
  end
649
900
  #:startdoc:
650
901
 
651
- # These are for rounding each value of a matrix
652
- def __list_unary_round__
902
+ # These are for rounding each value of a matrix. Takes an optional argument
903
+ def __list_unary_round__(precision)
653
904
  if self.complex_dtype?
654
- self.__list_map_stored__(nil) { |l| Complex(l.real.round, l.imag.round) }
905
+ self.__list_map_stored__(nil) { |l| Complex(l.real.round(precision), l.imag.round(precision)) }
655
906
  .cast(stype, dtype)
656
907
  else
657
- self.__list_map_stored__(nil) { |l| l.round }.cast(stype, dtype)
908
+ self.__list_map_stored__(nil) { |l| l.round(precision) }.cast(stype, dtype)
658
909
  end
659
910
  end
660
911
 
661
- def __yale_unary_round__
912
+ def __yale_unary_round__(precision)
662
913
  if self.complex_dtype?
663
- self.__yale_map_stored__ { |l| Complex(l.real.round, l.imag.round) }
914
+ self.__yale_map_stored__ { |l| Complex(l.real.round(precision), l.imag.round(precision)) }
664
915
  .cast(stype, dtype)
665
916
  else
666
- self.__yale_map_stored__ { |l| l.round }.cast(stype, dtype)
917
+ self.__yale_map_stored__ { |l| l.round(precision) }.cast(stype, dtype)
667
918
  end
668
919
  end
669
920
 
670
- def __dense_unary_round__
921
+ def __dense_unary_round__(precision)
671
922
  if self.complex_dtype?
672
- self.__dense_map__ { |l| Complex(l.real.round, l.imag.round) }
923
+ self.__dense_map__ { |l| Complex(l.real.round(precision), l.imag.round(precision)) }
673
924
  .cast(stype, dtype)
674
925
  else
675
- self.__dense_map__ { |l| l.round }.cast(stype, dtype)
926
+ self.__dense_map__ { |l| l.round(precision) }.cast(stype, dtype)
676
927
  end
677
928
  end
678
929
 
@@ -680,7 +931,7 @@ protected
680
931
  def dtype_for_floor_or_ceil
681
932
  if self.integer_dtype? or [:complex64, :complex128, :object].include?(self.dtype)
682
933
  return_dtype = dtype
683
- elsif [:float32, :float64, :rational32,:rational64, :rational128].include?(self.dtype)
934
+ elsif [:float32, :float64].include?(self.dtype)
684
935
  return_dtype = :int64
685
936
  end
686
937