nulin 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ require 'narray'
2
+
3
+ class NArray
4
+ def square?
5
+ shape = self.shape
6
+ rank == 2 && shape[0] == shape[1]
7
+ end
8
+
9
+ def real?
10
+ typecode == DFLOAT || typecode == SFLOAT
11
+ end
12
+
13
+ def self.build(typecode, *sizes)
14
+ nary = new(typecode, *sizes)
15
+ (0 ... nary.size).each{|i| nary[i] = yield(*nary.ndindex_from_1dindex(i)) }
16
+ nary
17
+ end
18
+
19
+ def ndindex_from_1dindex(i)
20
+ ret = []
21
+ shape.each{|n| ret << i%n; i /= n }
22
+ ret
23
+ end
24
+
25
+ def each_with_index
26
+ (0 ... size).each{|i| yield(self[i], *ndindex_from_1dindex(i)) }
27
+ end
28
+
29
+ def self.complex_typecode?(typecode)
30
+ typecode == NArray::DCOMPLEX || typecode == NArray::SCOMPLEX
31
+ end
32
+
33
+ def self.filled(typecode, data, *sizes)
34
+ naray = new(typecode, *sizes)
35
+ naray.fill(data)
36
+ naray
37
+ end
38
+ end
39
+
40
+ class NMatrix
41
+ def row_vector(i)
42
+ NVector.ref(NArray.ref(self)[true, i, false])
43
+ end
44
+
45
+ def row_vectors
46
+ d = self.shape[1]
47
+ (0 ... d).map{|i| row_vector(i) }
48
+ end
49
+
50
+ def column_vector(i)
51
+ NVector.ref(NArray.ref(self)[i, false])
52
+ end
53
+
54
+ def column_vectors
55
+ d = self.shape[0]
56
+ (0 ... d).map{|i| column_vector(i) }
57
+ end
58
+
59
+ def adjoint
60
+ ret = self.transpose; ret.conj!
61
+ ret
62
+ end
63
+
64
+ def as_vector
65
+ if rank != 2 || (shape[0] != 1 && shape[1] != 1)
66
+ raise(ArgumentError,
67
+ "NMatrix#as_vector: the matrix should be column matrix" +
68
+ "or row matrix")
69
+ end
70
+
71
+ NVector.ref(flatten)
72
+ end
73
+
74
+ def self.diagonal(a, shape=[a.size, a.size], typecode=nil)
75
+ typecode ||= array2typecode(a)
76
+ m = NMatrix.new(typecode, *shape)
77
+ a = a.refer if a.kind_of?(NArray)
78
+ m.diagonal!(a)
79
+
80
+ m
81
+ end
82
+
83
+ def self.array2typecode(ary)
84
+ if ary.kind_of?(NArray)
85
+ ary.typecode
86
+ else
87
+ case ary[0]
88
+ when Float then NArray::DFLOAT
89
+ when Integer then NArray::INT
90
+ when Complex then NArray::DCOMPLEX
91
+ else NArray::OBJECT
92
+ end
93
+ end
94
+ end
95
+
96
+ def self.I(n, typecode=NArray::DFLOAT)
97
+ m = NMatrix.new(typecode, n, n)
98
+ m.I
99
+
100
+ m
101
+ end
102
+
103
+ end
104
+
105
+ class NVector
106
+ def normalize
107
+ self / norm
108
+ end
109
+
110
+ def norm
111
+ Math.sqrt(self.mul_add(self.conjugate, 0).real)
112
+ end
113
+ end
@@ -0,0 +1,51 @@
1
+ require 'narray'
2
+ require 'narray_extext'
3
+ require 'singleton'
4
+ require 'nulin_native'
5
+
6
+ module NuLin
7
+ class DimensionError < StandardError
8
+ end
9
+
10
+ class LinalgError < StandardError
11
+ end
12
+
13
+ TYPECODES = [NArray::SFLOAT, NArray::DFLOAT, NArray::SCOMPLEX, NArray::DCOMPLEX]
14
+
15
+ LAPACK_PREFIX = {
16
+ NArray::SFLOAT => "s", NArray::DFLOAT => "d",
17
+ NArray::SCOMPLEX => "c", NArray::DCOMPLEX => "z",
18
+ }
19
+
20
+ module_function
21
+ def to_real_typecode(typecode)
22
+ return NArray::DFLOAT if typecode == NArray::DCOMPLEX
23
+ return NArray::SFLOAT if typecode == NArray::SCOMPLEX
24
+ return typecode
25
+ end
26
+
27
+ def to_complex_typecode(typecode)
28
+ return NArray::SCOMPLEX if typecode == NArray::SFLOAT
29
+ return NArray::DCOMPLEX
30
+ end
31
+
32
+ module Native
33
+ module_function
34
+ def call(typecode, name, *args)
35
+ fun_name = LAPACK_PREFIX[typecode] + name
36
+ retvals = __send__(fun_name, *args)
37
+ info = retvals ? retvals.last : 0
38
+ raise LinalgError, "#{fun_name}: errno #{info}" if info != 0
39
+
40
+ return retvals
41
+ end
42
+ end
43
+ end
44
+
45
+ require 'nulin/eigensystem'
46
+ require 'nulin/svd'
47
+ require 'nulin/lls'
48
+ require 'nulin/qr'
49
+ require 'nulin/det'
50
+ require 'nulin/cholesky'
51
+ require 'nulin/gemm'
@@ -0,0 +1,62 @@
1
+ module NuLin
2
+ module_function
3
+ # Compute the Cholesky decomposition for a positive definite
4
+ # symmetric/Hermitian `matrix`.
5
+ #
6
+ # Options
7
+ # * :type (default :U) - select whether the result matrix
8
+ # is upper(:U) or lower(:L).
9
+ #
10
+ # If the matrix is not positive definite, NuLin::LinalgError is raised.
11
+ #
12
+ # @note This method doesn't validate the matrix is symmetric/Hermitian.
13
+ # The only upper/lower half of the matrix is used for the computation.
14
+ # @param matrix[NMatrix] target matrix
15
+ # @param options[Hash] option hash
16
+ def cholesky(matrix, options={})
17
+ unless matrix.square?
18
+ raise DimensionError, "Cholesky decomposition is computable for a square matrix"
19
+ end
20
+
21
+ return Cholesky.new(matrix, options).result
22
+ end
23
+ end
24
+
25
+ class NuLin::Cholesky
26
+ def initialize(matrix, options)
27
+ @a = matrix
28
+ @typecode = matrix.typecode
29
+ case options.fetch(:type, :U)
30
+ when :U then @upper_triangular = true
31
+ when :L then @upper_triangular = false
32
+ else raise ArgumentError, "NuLin.cholesky: :type argument should be :U or :L"
33
+ end
34
+ compute
35
+ end
36
+
37
+ attr_reader :result
38
+
39
+ def compute
40
+ n, = @a.shape
41
+ @result = @a.transpose
42
+ NuLin::Native.call(@typecode, "potrf", @upper_triangular ? "L" : "U",
43
+ n, @result, n, 0)
44
+ if @upper_triangular
45
+ clear_lower
46
+ else
47
+ clear_upper
48
+ end
49
+
50
+ @result.conj!
51
+ end
52
+
53
+ def clear_lower
54
+ n, = @result.shape
55
+ 0.upto(n-1){|i| (i+1).upto(n-1){|j| @result[i, j] = 0.0 } }
56
+ end
57
+
58
+ def clear_upper
59
+ n, = @result.shape
60
+ 0.upto(n-1){|i| (i+1).upto(n-1){|j| @result[j, i] = 0.0 } }
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ module NuLin
2
+ module_function
3
+ # Compute the determinant of the `matrix` and return it.
4
+ #
5
+ # You get an exception NuLin::DimensionError if the matrix is not square
6
+ # The determinant of given matrix is computed using QR factorization.
7
+ #
8
+ # @param matrix[NMatrix] target matrix
9
+ def det(matrix)
10
+ unless matrix.square?
11
+ raise DimensionError, "Determinant is computable only on a square matrix"
12
+ end
13
+ n, = matrix.shape
14
+ r = qr(matrix).R
15
+ (0 ... n).inject(1.0){|u, i| u*r[i,i] }
16
+ end
17
+ end
@@ -0,0 +1,191 @@
1
+
2
+ module NuLin
3
+ module_function
4
+
5
+ # Return the eigensystem(a pair of eigenvalues and left/right eigenvectors)
6
+ # of `matrix` as an instance of (a subclass of) EigenDecomposition
7
+ #
8
+ # You can call this with some `options` as follows
9
+ # * :use_complex (default true) Return complex NArray objects
10
+ # If you enable this, the return object (EigenDecomposition)
11
+ # holds complex NArray objects.
12
+ # If you disable this, it is assumed that all eigenvalues are
13
+ # real and the return object holds the real NArray objects.
14
+ # If you disable this but there is a complex eigenvalue,
15
+ # the exception ArgumentError is raised.
16
+ # This options is ignored if `matrix` is complex.
17
+ # * :use_right (default true) - Compute right eigenvectors
18
+ # * :use_left (default true) - Compute left eigenvectors
19
+ # @param matrix [NMatrix] target matrix
20
+ # @param options [Hash] options
21
+ def eigensystem(matrix, options={})
22
+ unless matrix.square?
23
+ raise DimensionError, "The eigensystem is computable only for a square matrix"
24
+ end
25
+
26
+ case
27
+ when matrix.real?
28
+ RealEigenDecomposition.new(matrix, options)
29
+ when matrix.complex?
30
+ ComplexEigenDecomposition.new(matrix, options)
31
+ else
32
+ raise ArgumentError, "The eigensystem is comptable for a complex/float matrix"
33
+ end
34
+ end
35
+
36
+ class EigenDecomposition
37
+ # Dimension of the linear space
38
+ attr_reader :dim
39
+ # All eigenvalues as NArray object
40
+ attr_reader :eigenvalues
41
+ # Left eigenvectors as NMatrix
42
+ attr_reader :left
43
+ # Right eigenvectors as NMatrix
44
+ attr_reader :right
45
+
46
+ # Returns all right eigenvectors as the array of NVector
47
+ def right_eigenvectors
48
+ @right_eigenvectors ||= @right.column_vectors
49
+ end
50
+
51
+ # Returns all right eigenvectors as the array of NVector
52
+ def left_eigenvectors
53
+ @left_eigenvectors ||= @left.row_vectors
54
+ end
55
+ end
56
+
57
+ class RealEigenDecomposition < EigenDecomposition
58
+ def initialize(matrix, opts)
59
+ @matrix = matrix
60
+ @dim = matrix.shape[0]
61
+ @use_left = opts.fetch(:use_left, true)
62
+ @use_right = opts.fetch(:use_right, true)
63
+ @use_complex = opts.fetch(:use_complex, true)
64
+ @typecode = matrix.typecode
65
+ @complex_typecode = NuLin.to_complex_typecode(@typecode)
66
+
67
+ compute
68
+ end
69
+
70
+ private
71
+ def compute
72
+ n, = @matrix.shape
73
+ @wr = NArray.new(@typecode, n)
74
+ @wi = NArray.new(@typecode, n)
75
+ ldvl = @use_right ? n : 1
76
+ @vl = NArray.new(@typecode, ldvl, n)
77
+ ldvr = @use_left ? n : 1
78
+ @vr = NArray.new(@typecode, ldvr, n)
79
+ lwork = (@use_left || @use_right) ? 4*n : 3*n
80
+ work = NArray.new(@typecode, lwork)
81
+
82
+ NuLin::Native.call(@typecode, "geev",
83
+ @use_right ? 'V' : 'N', @use_left ? 'V' : 'N',
84
+ n, @matrix.dup, n, @wr, @wi, @vl, ldvl, @vr, ldvr,
85
+ work, lwork, 0)
86
+
87
+ if !@use_complex && !@wi.eq(0.0).all?
88
+ raise ArgumentError, "There is any complex eigenvalue"
89
+ end
90
+ compute_eigenvalues
91
+ compute_right if @use_right
92
+ compute_left if @use_left
93
+ end
94
+
95
+ def compute_eigenvalues
96
+ @eigenvalues = @use_complex ? complex_eigenvalues : real_eigenvalues
97
+ end
98
+
99
+ def real_eigenvalues
100
+ @wr
101
+ end
102
+
103
+ def complex_eigenvalues
104
+ eigenvalues = NArray.new(@complex_typecode, @dim)
105
+ eigenvalues[] = @wr
106
+ eigenvalues.imag = @wi
107
+ eigenvalues
108
+ end
109
+
110
+ # compute right eigenvectors and right eigenvector matrix
111
+ def compute_right
112
+ unless @use_complex
113
+ @right = NMatrix.ref(@vl).transpose
114
+ return
115
+ end
116
+
117
+ @right_eigenvectors = extract_complex_eigenvectors(@vl, -1)
118
+ @right = NMatrix.new(@complex_typecode, @dim, @dim)
119
+ @right_eigenvectors.map.with_index do |v, i|
120
+ @right[i, true] = v
121
+ end
122
+ end
123
+
124
+ # Extract eigenvectors from the result(vl or vr) of NumRu::Lapack.dgeev
125
+ def extract_complex_eigenvectors(m, sign)
126
+ eigenvectors = []
127
+ (0 ... @dim).each do |i|
128
+ if @wi[i] == 0.0
129
+ eigenvectors << NVector.ref(m[true, i])
130
+ elsif @wi[i] > 0.0
131
+ v = NVector.new(@complex_typecode, @dim)
132
+ v[] = m[true, i]; v.imag = m[true, i+1]
133
+ v.conj! if sign < 0
134
+ eigenvectors << v
135
+ else
136
+ eigenvectors << eigenvectors.last.conj
137
+ end
138
+ end
139
+
140
+ eigenvectors
141
+ end
142
+
143
+ # compute left eigenvectors and left eigenvector matrix
144
+ def compute_left
145
+ unless @use_complex
146
+ @left = NMatrix.ref(@vr)
147
+ return
148
+ end
149
+
150
+ @left_eigenvectors = extract_complex_eigenvectors(@vr, 1)
151
+ @left = NMatrix.new(@complex_typecode, @dim, @dim)
152
+ @left_eigenvectors.map.with_index do |v, i|
153
+ @left[true, i] = v
154
+ end
155
+ end
156
+ end
157
+
158
+ class ComplexEigenDecomposition < EigenDecomposition
159
+ def initialize(matrix, opts)
160
+ @matrix = matrix
161
+ @dim = matrix.shape[0]
162
+ @use_left = opts.fetch(:use_left, true)
163
+ @use_right = opts.fetch(:use_right, true)
164
+ @typecode = matrix.typecode
165
+
166
+ compute
167
+ end
168
+
169
+ private
170
+ # TODO: @left should be adjoint or not?
171
+ def compute
172
+ n, = @matrix.shape
173
+ @eigenvalues = NArray.new(@typecode, n)
174
+ ldvl = @use_right ? n : 1
175
+ vl = NMatrix.new(@typecode, ldvl, n)
176
+ ldvr = @use_left ? n : 1
177
+ vr = NMatrix.new(@typecode, ldvr, n)
178
+ lwork = 2*n
179
+ work = NArray.new(@typecode, lwork)
180
+ rwork = NArray.new(@typecode, 2*n)
181
+
182
+ NuLin::Native.call(@typecode, "geev",
183
+ @use_right ? 'V' : 'N', @use_left ? 'V' : 'N',
184
+ n, @matrix.dup, n, @eigenvalues,
185
+ vl, ldvl, vr, ldvr, work, lwork, rwork, 0)
186
+
187
+ @right = vl.adjoint
188
+ @left = vr
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,53 @@
1
+ module NuLin
2
+ module_function
3
+
4
+ # Return alpha*a*b + beta*c
5
+ def multiply_matrix_and_add(alpha, a, b, beta, c, options={})
6
+ c = c.dup if options.fetch(:overwrite_c, false)
7
+ trans_a = options.fetch(:trans_a, nil)
8
+ trans_b = options.fetch(:trans_b, nil)
9
+
10
+ if !trans_a
11
+ k, n = a.shape
12
+ else
13
+ n, k = a.shape
14
+ end
15
+ lda = a.shape[0]
16
+
17
+ if !trans_b
18
+ m, kk = b.shape
19
+ else
20
+ kk, m = b.shape
21
+ end
22
+ ldb = b.shape[0]
23
+
24
+ mm, nn = c.shape
25
+ ldc = mm
26
+
27
+ unless k == kk && m == mm && n == nn
28
+ raise DimensionError, "Invalid matrix shape"
29
+ end
30
+
31
+ NuLin::Native.call(a.typecode, "gemm",
32
+ mm_transpose_character(trans_b), mm_transpose_character(trans_a),
33
+ m, n, k, alpha, b, ldb, a, lda, beta, c, ldc)
34
+
35
+ c
36
+ end
37
+
38
+ def mm_transpose_character(t)
39
+ case t
40
+ when nil, false
41
+ "N"
42
+ when :T, :transpose
43
+ "T"
44
+ when :C, :H, :adjoint
45
+ "C"
46
+ else
47
+ raise ArgumentError, "Unkdnown transposing spec: #{t.inspect}"
48
+ end
49
+ end
50
+
51
+ alias mm multiply_matrix_and_add
52
+ module_function :mm
53
+ end