nulin 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
1
+
2
+ module NuLin
3
+ module_function
4
+ # Solve the linear least square problem
5
+ # of min_x |a*x - b|.
6
+ #
7
+ # The solution is returned as an instance of NuLin::LLS.
8
+ #
9
+ # When n >= m and rank(a) = n, the problem has a unique solution.
10
+ #
11
+ # When n < m and rank(a) = n, the equation a*x - b = 0 has
12
+ # infinitely many solutions. In this case, we consider the problem
13
+ # of finding the (unique) solution which minimizes |x|.
14
+ #
15
+ # If rank(a)=min(m, n), we can find the solution of the two
16
+ # problems using QR or LQ factorization.
17
+ #
18
+ # In the general case, if rank(a) < min(m, n), the above two problems
19
+ # don't have a unique solution. Therefore we consider the problem
20
+ # of finding x which minimize |a*x-b| and |x|^2. We can solve the problem
21
+ # using complete orthogonal factorization or SVD.
22
+ #
23
+ # options
24
+ # * :algorithm (default :qr) - select algorithm :qr, :svd
25
+ # * :rcond (default -1.0) - The threshold to determine effective rank of
26
+ # matrix a. This parameter is used only for svd algorithm.
27
+ # The number of singular values greater than
28
+ # rcond * (the largest singular value) is regarded as the effective rank.
29
+ #
30
+ # Example
31
+ # # Using QR/LQ algorithm
32
+ # x = NuLin.linear_least_square(a, b).solution
33
+ #
34
+ # # Using SVD
35
+ # x = NuLin.linear_least_square(a, b, algorithm: :svd, rcond: 0.0001).solution
36
+ #
37
+ # @param a[NMatrix] a matrix whose shape is [n, m]
38
+ # @param b[NVector] a vector whose shape is [m]
39
+ # @param options[Hash] computation options
40
+ def linear_least_square(a, b, options={})
41
+ case options.fetch(:algorithm, :qr)
42
+ when :qr
43
+ NuLin::LLS_QR.new(a, b, options)
44
+ when :svd
45
+ NuLin::LLS_SVD.new(a, b, options)
46
+ end
47
+ end
48
+
49
+ def lls(*args); linear_least_square(*args); end
50
+ end
51
+
52
+ class NuLin::LLS
53
+ def initialize(a, b, options)
54
+ if !valid_matrix?(a, b)
55
+ raise NuLin::DimensionError, "Invalid matrix/vector shape"
56
+ end
57
+ @a = a.transpose
58
+ @b_is_rank1 = b.rank == 1
59
+ @b = (@b_is_rank1) ? b.reshape(b.shape[0], 1) : b
60
+
61
+ @typecode = a.typecode
62
+ end
63
+
64
+ def valid_matrix?(a, b)
65
+ a.rank == 2 &&
66
+ (b.rank == 1 || b.rank == 2) &&
67
+ a.shape[1] == b.shape[0] &&
68
+ a.typecode == b.typecode
69
+ end
70
+
71
+ # Return the solution of the LLS problem as NVector.
72
+ attr_reader :solution
73
+ end
74
+
75
+ class NuLin::LLS_QR < NuLin::LLS
76
+ def initialize(a, b, options)
77
+ super
78
+ compute
79
+ end
80
+
81
+ def compute
82
+ m, n = @a.shape
83
+ lda = m
84
+ ldb = [1, m, n].max
85
+ r, nrhs = @b.shape
86
+ b = NVector.new(@typecode, ldb, nrhs)
87
+ b[0...m, 0...nrhs] = @b
88
+ lwork = [1, [m,n].min + [[m,n].min, nrhs].max].max
89
+ work = NArray.new(@typecode, lwork)
90
+ NuLin::Native.call(@typecode, "gels", "N", m, n, nrhs, @a, lda, b, ldb,
91
+ work, lwork, 0)
92
+ @solution = b[0...n, true]
93
+ @solution.flatten! if @b_is_rank1
94
+ end
95
+ end
96
+
97
+ class NuLin::LLS_SVD < NuLin::LLS
98
+ def initialize(a, b, options)
99
+ super
100
+ @rcond = options.fetch(:rcond, -1.0)
101
+ compute
102
+ end
103
+
104
+ # Singular values of the matrix as NArray.
105
+ # The values are ordered decreasingly.
106
+ attr_reader :singular_values
107
+ # The effective rank of the matrix.
108
+ attr_reader :rank
109
+
110
+ def compute
111
+ m, n = @a.shape
112
+ r, nrhs = @b.shape
113
+ k = [m, n].min
114
+ lda = [1, m].max
115
+ ldb = [1, m, n].max
116
+ b = NVector.new(@typecode, ldb, nrhs)
117
+ b[0...m, 0...nrhs] = @b
118
+ s = NArray.new(NuLin.to_real_typecode(@typecode), k)
119
+ lwork = 3*k + [2*k, m, n, nrhs].max + 1
120
+ work = NVector.new(@typecode, lwork)
121
+ if @a.complex?
122
+ rwork = [NArray.new(@typecode, 5*k)]
123
+ else
124
+ rwork = []
125
+ end
126
+ @rank, = NuLin::Native.call(@typecode, "gelss", m, n, nrhs, @a, lda, b, ldb, s,
127
+ @rcond, 0, work, lwork, *rwork, 0)
128
+
129
+ @singular_values = s
130
+ @solution = b[0...n, true]
131
+ @solution.flatten! if @b_is_rank1
132
+ end
133
+ end
@@ -0,0 +1,68 @@
1
+
2
+ module NuLin
3
+ module_function
4
+ # Compute the QR Decomposition of `matrix`
5
+ # and return the result as NuLin::QR object.
6
+ #
7
+ # You can call this with `options`, but now the argument is
8
+ # ignored.
9
+ #
10
+ # @param matrix[NMatrix] The matrix
11
+ # @param options[Hash] computation options, not used now.
12
+ def qr(matrix, options={})
13
+ NuLin::QR.new(matrix, options)
14
+ end
15
+ end
16
+
17
+ class NuLin::QR
18
+ def initialize(matrix, options)
19
+ @matrix = matrix
20
+ @typecode = matrix.typecode
21
+
22
+ compute
23
+ end
24
+
25
+ # The orthgonal/unitary matrix Q
26
+ attr_reader :Q
27
+ # The tranposed/adjoint matrix of Q
28
+ attr_reader :Qt
29
+ # The upper triangular matrix R
30
+ attr_reader :R
31
+
32
+ private
33
+ def compute
34
+ m, n = @matrix.shape
35
+ lwork = n
36
+
37
+ q = base_q
38
+ tau = NArray.new(@typecode, n)
39
+ work = NArray.new(@typecode, lwork)
40
+
41
+ NuLin::Native.call(@typecode, "gelqf", n, n, q, n, tau, work, lwork, 0)
42
+ NuLin::Native.call(@typecode, glq_name, n, n, n, q, n, tau, work, lwork, 0)
43
+
44
+ @Q = q
45
+ @Qt = q.adjoint
46
+ @R = @Qt * @matrix
47
+ end
48
+
49
+ def glq_name
50
+ if NArray.complex_typecode?(@typecode)
51
+ "unglq"
52
+ else
53
+ "orglq"
54
+ end
55
+ end
56
+
57
+ def base_q
58
+ m, n = @matrix.shape
59
+ case
60
+ when m >= n
61
+ @matrix[0...n, 0...n]
62
+ when m < n
63
+ bq = NMatrix.new(@typecode, n, n)
64
+ bq[0...m, 0...n] = @matrix
65
+ bq
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,84 @@
1
+
2
+ module NuLin
3
+ module_function
4
+ # Compute the Singular Value Decomposition of `matrix`
5
+ # and return the result as an instance of SVD
6
+ #
7
+ # You can call this with some `options` as follows
8
+ # * :use_U (default true) Compute the matrix U
9
+ # * :use_V (default true) Compute the matrix V and V^t
10
+ # * :full_matrix (default true)
11
+ # From the theory of SVD,
12
+ # Any matrix is decomposable into U * sigma * transpose(V)
13
+ # where
14
+ # * U, V: orthogonal(real) or unitary(complex) matrices
15
+ # * sigma: diagonal matrix
16
+ #
17
+ # @param matrix[NMatrix] target matrix
18
+ # @param options[Hash] computation options
19
+ def svd(matrix, options={})
20
+ SVD.new(matrix, options)
21
+ end
22
+ end
23
+
24
+ class NuLin::SVD
25
+ def initialize(matrix, opts)
26
+ @matrix = matrix
27
+ @use_U = opts.fetch(:use_U, true)
28
+ @use_V = opts.fetch(:use_V, true)
29
+ @full_matrix = opts.fetch(:full_matrix, true)
30
+ @typecode = matrix.typecode
31
+
32
+ compute
33
+ end
34
+
35
+ # The matrix U
36
+ attr_reader :U
37
+ # Transposed matrix of the matrix V
38
+ attr_reader :Vt
39
+ # Singular values as NArray object
40
+ attr_reader :singular_values
41
+ # The matrix V
42
+ def V
43
+ return nil unless @use_V
44
+ @V ||= @Vt.transpose
45
+ end
46
+ # The matrix sigma
47
+ def sigma
48
+ return @sigma if @sigma
49
+ @sigma = NMatrix.new(@matrix.typecode, *@matrix.shape)
50
+ @sigma.diagonal!(@singular_values.refer)
51
+ end
52
+
53
+ private
54
+ def compute
55
+ m, n = @matrix.shape
56
+ k = [m, n].min
57
+ l = [m, n].max
58
+ s = NArray.new(NuLin.to_real_typecode(@typecode), k)
59
+ ldu = @use_V ? m : 1
60
+ u = NMatrix.new(@typecode, ldu, @use_V ? (@full_matrix ? m : k) : 0)
61
+ ldvt = @use_U ? (@full_matrix ? n : k) : 1
62
+ vt = NMatrix.new(@typecode, ldvt, @use_V ? n : 1)
63
+ # MAX(1,3*MIN(M,N)+MAX(M,N),5*MIN(M,N)) for real
64
+ # MAX(1,2*MIN(M,N)+MAX(M,N)) for complx
65
+ lwork = @matrix.real? ? [1, 3*k+l, 5*k].max : [1, 2*k + l].max
66
+ work = NArray.new(@typecode, lwork)
67
+ if @matrix.complex?
68
+ rwork = [NArray.new(@typecode, 5*k)]
69
+ else
70
+ rwork = []
71
+ end
72
+
73
+ NuLin::Native.call(@typecode, "gesvd", job_str(@use_V), job_str(@use_U),
74
+ m, n, @matrix.dup, m, s, u, ldu, vt, ldvt,
75
+ work, lwork, *rwork, 0)
76
+ @U = vt if @use_U
77
+ @Vt = u if @use_V
78
+ @singular_values = s
79
+ end
80
+
81
+ def job_str(using)
82
+ using ? (@full_matrix ? "A" : "S") : "N"
83
+ end
84
+ end
@@ -0,0 +1,17 @@
1
+ require 'test-unit'
2
+
3
+ module Test::Unit::Assertions
4
+ eval <<-EOS
5
+ def assert_narray_in_delta(expected, actual, delta=0.001, message=nil)
6
+ failure_message = build_message(message, <<-EOT, expected, delta, actual)
7
+ <?> +-? expected but was
8
+ <?>
9
+ EOT
10
+ assert_block(failure_message) do
11
+ ((expected - actual).abs < delta).all?
12
+ end
13
+ end
14
+ EOS
15
+ end
16
+
17
+ exit Test::Unit::AutoRunner.run(true, ".")
@@ -0,0 +1,39 @@
1
+ require 'test-unit'
2
+ require 'nulin'
3
+ require 'complex'
4
+
5
+ class TestNuLin_cholesky < Test::Unit::TestCase
6
+ def test_cholesky
7
+ m = NMatrix[ [ 1.18327, -0.484033, -0.675672, -1.33157 ],
8
+ [ -0.484033, 1.3897, 0.302005, 1.14085 ],
9
+ [ -0.675672, 0.302005, 0.964183, 0.61185 ],
10
+ [ -1.33157, 1.14085, 0.61185, 1.84453 ] ]
11
+ u = NuLin.cholesky(m, type: :U)
12
+ assert_narray_in_delta(m, u.transpose * u)
13
+ u.each_with_index do |v, i, j|
14
+ assert_in_delta(0.0, v) if i < j
15
+ end
16
+
17
+ l = NuLin.cholesky(m, type: :L)
18
+ assert_narray_in_delta(m, l * l.transpose)
19
+ l.each_with_index do |v, i, j|
20
+ assert_in_delta(0.0, v) if i > j
21
+ end
22
+
23
+ m = NMatrix[ [ 5.4452+0.0.i, 1.53215-1.46758.i, 3.47221+1.94952.i ],
24
+ [ 1.53215+1.46758.i, 8.51107+0.0.i, -5.64531+1.63224.i ],
25
+ [ 3.47221-1.94952.i, -5.64531-1.63224.i, 8.67626+0.0.i ] ]
26
+
27
+ u = NuLin.cholesky(m, type: :U)
28
+ assert_narray_in_delta(m, u.adjoint * u)
29
+ u.each_with_index do |v, i, j|
30
+ assert_in_delta(0.0, v.abs) if i < j
31
+ end
32
+
33
+ l = NuLin.cholesky(m, type: :L)
34
+ assert_narray_in_delta(m, l * l.adjoint)
35
+ l.each_with_index do |v, i, j|
36
+ assert_in_delta(0.0, v.abs) if i > j
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ require 'test-unit'
2
+ require 'nulin'
3
+
4
+ class TestNuLin_det < Test::Unit::TestCase
5
+ def test_det
6
+ m = NMatrix[[1.0, 0.8, -0.1],
7
+ [-0.4, 0.3, 1.2],
8
+ [-0.8, -0.3, -0.6]]
9
+ assert_in_delta(-0.816, NuLin.det(m))
10
+ end
11
+ end
@@ -0,0 +1,71 @@
1
+ require 'test-unit'
2
+ require 'nulin'
3
+
4
+ class TestNuLin_eigensystem < Test::Unit::TestCase
5
+ def test_eigensystem
6
+ m = NMatrix[[1.2, -4.2, 2.9],
7
+ [0.92, 1.5, 3.0],
8
+ [1.0, -0.5, -2.2]]
9
+
10
+ check_eigensystem(m, NArray::COMPLEX)
11
+ end
12
+
13
+ def test_for_real_eigensystem
14
+ m = NMatrix[[6.0, -3, -7],
15
+ [ -1, 2, 1],
16
+ [ 5, -3, -6]]
17
+ eig = NuLin.eigensystem(m, use_complex: false)
18
+
19
+ assert_equal(3, eig.dim)
20
+ assert_equal(NArray::FLOAT, eig.eigenvalues.typecode)
21
+ assert_equal(NArray::FLOAT, eig.right.typecode)
22
+ assert_equal(NArray::FLOAT, eig.left.typecode)
23
+ end
24
+
25
+ def test_eigensystem_sfloat
26
+ m = NMatrix[[1.2, -4.2, 2.9],
27
+ [0.92, 1.5, 3.0],
28
+ [1.0, -0.5, -2.2]]
29
+ md = NMatrix.sfloat(3, 3)
30
+ md[] = m
31
+
32
+ check_eigensystem(md, NArray::SCOMPLEX)
33
+ end
34
+
35
+ def test_eigensystem_complex
36
+ m = NMatrix[[Complex(1.2, 0.5), Complex(-3.8, -0.9), Complex(1.8)],
37
+ [Complex(0, -2.3), Complex(1.5, 1.0), Complex(1.2, 1.2)],
38
+ [Complex(-1.9, 0.1), Complex(0.0), Complex(-4.7, 0.7)]]
39
+ check_eigensystem(m, NArray::COMPLEX)
40
+ end
41
+
42
+ def test_eigensystem_scomplex
43
+ m = NMatrix[[Complex(1.2, 0.5), Complex(-3.8, -0.9), Complex(1.8)],
44
+ [Complex(0, -2.3), Complex(1.5, 1.0), Complex(1.2, 1.2)],
45
+ [Complex(-1.9, 0.1), Complex(0.0), Complex(-4.7, 0.7)]]
46
+ md = NMatrix.scomplex(3, 3); md[] = m
47
+
48
+ check_eigensystem(md, NArray::SCOMPLEX)
49
+ end
50
+
51
+ def check_eigensystem(m, typecode)
52
+ eig = NuLin.eigensystem(m)
53
+
54
+ assert_equal(3, eig.dim)
55
+ assert_equal(typecode, eig.eigenvalues.typecode)
56
+ assert_equal(typecode, eig.right.typecode)
57
+ assert_equal(typecode, eig.left.typecode)
58
+
59
+ eig.dim.times do |i|
60
+ v1 = m * eig.right.column_vector(i)
61
+ v2 = eig.eigenvalues[i] * eig.right.column_vector(i)
62
+ assert_narray_in_delta(v1, v2)
63
+ assert_equal(eig.right.column_vector(i), eig.right_eigenvectors[i])
64
+
65
+ w1 = eig.left.row_vector(i) * m
66
+ w2 = eig.left.row_vector(i) * eig.eigenvalues[i]
67
+ assert_narray_in_delta(w1, w2)
68
+ assert_equal(eig.left.row_vector(i), eig.left_eigenvectors[i])
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,57 @@
1
+ require 'nulin'
2
+
3
+ require 'test-unit'
4
+
5
+ class TestNuLin_mm < Test::Unit::TestCase
6
+ def test_mm
7
+ alpha = 1.4; beta = 0.75
8
+ a = NMatrix[[0.2, 0, -1.3],
9
+ [3.1, 0.92, -1.2],
10
+ [-0.9, 1.0, 0.0],
11
+ [-1.2, 1.1, 3.1]]
12
+ b = NMatrix[[0.2, 0.3, -0.123, -0.01, 0.02],
13
+ [0.32, 0.2, 0.11, -0.12, 1.22],
14
+ [0.2, 1.2, -0.3, 0.4, 1.1]]
15
+ c = NMatrix[ [ -0.809611, 0.308441, 0.178653, 0.14375, 1.51829 ],
16
+ [ -0.438721, -2.22648, 1.19524, -0.767709, 0.0279701 ],
17
+ [ -0.522194, 0.807863, -0.39172, -0.679458, -0.534994 ],
18
+ [ -0.0155417, -1.34528, -1.08651, -0.686664, 1.044 ] ]
19
+
20
+ assert_narray_in_delta(alpha*a*b + beta*c, NuLin.mm(alpha, a, b, beta, c))
21
+
22
+ a = a.to_type(NArray::SFLOAT)
23
+ b = b.to_type(NArray::SFLOAT)
24
+ c = c.to_type(NArray::SFLOAT)
25
+
26
+ assert_narray_in_delta(alpha*a*b + beta*c, NuLin.mm(alpha, a, b, beta, c))
27
+
28
+ alpha = 1.4 + 1.2.i; beta = 0.75 - 0.4.i
29
+
30
+ a = NMatrix[ [ 0.125503+0.180057.i, -0.567269+1.70667.i, -1.15548+1.95302.i ],
31
+ [ 0.59267+0.828465.i, 0.46672-0.0995614.i, -0.635015-1.14206.i ],
32
+ [ 0.330225+0.122008.i, -0.932912-2.2586.i, -0.13695+1.22169.i ],
33
+ [ -0.324652+0.629844.i, -0.276656-0.148201.i, -0.919279-1.76697.i ] ]
34
+ b = NMatrix[ [-0.547 + 0.251.i, -0.202 + 0.206.i, 1.635 + -0.312.i, -0.463 + -0.604.i, -0.440 + 1.257.i],
35
+ [-0.250 + 0.333.i, -0.220 + 0.140.i, 0.226 + -0.006.i, 1.392 + -0.191.i, 1.113 + 0.915.i],
36
+ [0.564 + -1.962.i, -0.988 + -0.074.i, 0.395 + -1.089.i, 0.445 + 0.741.i, 0.879 + -1.495.i]]
37
+
38
+ c = NMatrix[ [-1.192 + 0.398.i,0.595 + -0.781.i,-0.936 + -2.004.i,-1.110 + -0.091.i,-1.176 + -1.259.i,],
39
+ [-0.612 + -0.267.i,-0.704 + 2.284.i,-0.816 + -1.802.i,-0.621 + 0.207.i,0.858 + 0.094.i,],
40
+ [0.185 + 0.021.i,-0.935 + -0.658.i,1.179 + 2.649.i,1.018 + -1.135.i,0.532 + -0.287.i,],
41
+ [1.378 + 0.254.i,1.047 + 0.024.i,-1.637 + 1.171.i,0.864 + 1.070.i,2.015 + -0.231.i,]]
42
+
43
+ assert_narray_in_delta(alpha*a*b + beta*c, NuLin.mm(alpha, a, b, beta, c))
44
+ assert_narray_in_delta(alpha*a*b + beta*c,
45
+ NuLin.mm(alpha, a.transpose, b, beta, c, trans_a: :transpose))
46
+ assert_narray_in_delta(alpha*a*b + beta*c,
47
+ NuLin.mm(alpha, a.adjoint, b, beta, c, trans_a: :adjoint))
48
+ assert_narray_in_delta(alpha*a*b + beta*c,
49
+ NuLin.mm(alpha, a.transpose, b.adjoint, beta, c,
50
+ trans_a: :transpose, trans_b: :adjoint))
51
+ a = a.to_type(NArray::SCOMPLEX)
52
+ b = b.to_type(NArray::SCOMPLEX)
53
+ c = c.to_type(NArray::SCOMPLEX)
54
+
55
+ assert_narray_in_delta(alpha*a*b + beta*c, NuLin.mm(alpha, a, b, beta, c))
56
+ end
57
+ end