nulin 0.2

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