linalg 1.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.
- checksums.yaml +7 -0
- data/LICENSE +24 -0
- data/ext/g2c_typedefs.h +5 -0
- data/ext/lapack/extconf.rb +309 -0
- data/ext/lapack/include/BLAS.h +154 -0
- data/ext/lapack/include/LAPACK.h +1314 -0
- data/ext/lapack/main.c +49 -0
- data/ext/lapack/rb_lapack.h +36 -0
- data/ext/lapack/rb_lapack_c.c +11614 -0
- data/ext/lapack/rb_lapack_d.c +12663 -0
- data/ext/lapack/rb_lapack_s.c +12649 -0
- data/ext/lapack/rb_lapack_x.c +208 -0
- data/ext/lapack/rb_lapack_z.c +11614 -0
- data/ext/linalg/dcomplex.c +359 -0
- data/ext/linalg/dcomplex.h +40 -0
- data/ext/linalg/ddata.c +194 -0
- data/ext/linalg/extconf.rb +324 -0
- data/ext/linalg/linalg.c +55 -0
- data/ext/linalg/linalg.h +21 -0
- data/ext/linalg/xdata.c +21 -0
- data/ext/linalg/xdata.h +33 -0
- data/ext/linalg/xmatrix.c.tmpl +1630 -0
- data/ext/linalg/xmatrix.h.tmpl +77 -0
- data/ext/linalg/xmatrixc.c.tmpl +138 -0
- data/ext/linalg/xmatrixr.c.tmpl +130 -0
- data/lib/lapack.rb +87 -0
- data/lib/linalg.rb +9 -0
- data/lib/linalg/dcomplex.rb +17 -0
- data/lib/linalg/dmatrix.rb +29 -0
- data/lib/linalg/dmatrix/alias.rb +32 -0
- data/lib/linalg/dmatrix/cholesky.rb +52 -0
- data/lib/linalg/dmatrix/cond.rb +80 -0
- data/lib/linalg/dmatrix/det.rb +36 -0
- data/lib/linalg/dmatrix/eigen.rb +153 -0
- data/lib/linalg/dmatrix/fit.rb +281 -0
- data/lib/linalg/dmatrix/inverse.rb +78 -0
- data/lib/linalg/dmatrix/lu.rb +120 -0
- data/lib/linalg/dmatrix/main.rb +244 -0
- data/lib/linalg/dmatrix/norms.rb +88 -0
- data/lib/linalg/dmatrix/nullspace.rb +114 -0
- data/lib/linalg/dmatrix/qr.rb +129 -0
- data/lib/linalg/dmatrix/schur.rb +88 -0
- data/lib/linalg/dmatrix/solve.rb +78 -0
- data/lib/linalg/dmatrix/svd.rb +125 -0
- data/lib/linalg/exception.rb +32 -0
- data/lib/linalg/iterators.rb +221 -0
- data/lib/linalg/math.rb +23 -0
- data/lib/linalg/scomplex.rb +15 -0
- data/lib/linalg/version.rb +3 -0
- data/lib/linalg/xdata.rb +123 -0
- metadata +94 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2004-2008 by James M. Lawrence
|
3
|
+
#
|
4
|
+
# See LICENSE
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module Linalg
|
9
|
+
class DMatrix
|
10
|
+
#
|
11
|
+
# Returns a matrix whose columns form an orthogonal basis for the
|
12
|
+
# nullspace. (This is simply taken from <tt>vt.t</tt> in DMatrix#svd.)
|
13
|
+
# Returns +nil+ if the matrix is of full rank.
|
14
|
+
#
|
15
|
+
# For matrix +m+, a singular value less than or equal
|
16
|
+
# to <tt>m.norm*epsilon</tt> is considered a rank
|
17
|
+
# deficiency.
|
18
|
+
#
|
19
|
+
def nullspace(epsilon = (self.singleton_class.default_epsilon ||self.class.default_epsilon))
|
20
|
+
u, s, vt = self.svd
|
21
|
+
v = vt.transpose!
|
22
|
+
null_indexes = sv_null_indexes(s.diags, s[0,0]*epsilon)
|
23
|
+
|
24
|
+
if null_indexes.empty?
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
ns = DMatrix.reserve(v.vsize, null_indexes.size)
|
28
|
+
null_indexes.each_with_index { |e, j|
|
29
|
+
ns.replace_column(j, v.column(e))
|
30
|
+
}
|
31
|
+
ns
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Returns a matrix whose columns form an orthonormal basis
|
37
|
+
# for the span of the matrix.
|
38
|
+
#
|
39
|
+
# This is just the complement of the nullspace.
|
40
|
+
# See DMatrix#nullspace.
|
41
|
+
#
|
42
|
+
def rankspace(epsilon = (self.singleton_class.default_epsilon ||self.class.default_epsilon))
|
43
|
+
u, s, vt = self.svd
|
44
|
+
v = vt.transpose!
|
45
|
+
null_indexes = sv_null_indexes(s.diags, s[0,0]*epsilon)
|
46
|
+
|
47
|
+
if null_indexes.empty?
|
48
|
+
v
|
49
|
+
else
|
50
|
+
orth_indexes = (0...v.hsize).to_a - null_indexes
|
51
|
+
orth = DMatrix.reserve(v.vsize, orth_indexes.size)
|
52
|
+
orth_indexes.each_with_index { |e, j|
|
53
|
+
orth.replace_column(j, v.column(e))
|
54
|
+
}
|
55
|
+
orth
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Returns the column rank of the matrix.
|
61
|
+
# hsize == rank + nullity
|
62
|
+
#
|
63
|
+
# see DMatrix#nullspace
|
64
|
+
#
|
65
|
+
def rank(epsilon = (self.singleton_class.default_epsilon ||self.class.default_epsilon))
|
66
|
+
sv = self.sv
|
67
|
+
indexes = sv_null_indexes(sv, sv[0]*epsilon)
|
68
|
+
indexes ? (hsize - indexes.size) : 0
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Returns the nullity of the matrix (the dimension of the nullspace).
|
73
|
+
# hsize == rank + nullity
|
74
|
+
#
|
75
|
+
# see DMatrix#nullspace
|
76
|
+
#
|
77
|
+
def nullity(epsilon = self.class.default_epsilon)
|
78
|
+
sv = self.sv
|
79
|
+
indexes = sv_null_indexes(sv, sv[0]*epsilon)
|
80
|
+
indexes ? indexes.size : 0
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# True if the rank less than number of columns.
|
85
|
+
#
|
86
|
+
# see DMatrix#nullspace
|
87
|
+
#
|
88
|
+
def singular?(epsilon = (self.singleton_class.default_epsilon ||self.class.default_epsilon))
|
89
|
+
not regular?(epsilon)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# True if the rank is equal to the number of columns.
|
94
|
+
#
|
95
|
+
# see DMatrix#nullspace
|
96
|
+
#
|
97
|
+
def regular?(epsilon = (self.singleton_class.default_epsilon ||self.class.default_epsilon))
|
98
|
+
square? and rank(epsilon) == hsize
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def sv_null_indexes(sv, epsilon)
|
104
|
+
nulls = []
|
105
|
+
sv.each_with_index { |e, i|
|
106
|
+
nulls << i if e.abs <= epsilon
|
107
|
+
}
|
108
|
+
nulls += (self.vsize...self.hsize).to_a
|
109
|
+
nulls
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2004-2008 by James M. Lawrence
|
3
|
+
#
|
4
|
+
# See LICENSE
|
5
|
+
#
|
6
|
+
|
7
|
+
module Linalg
|
8
|
+
class DMatrix
|
9
|
+
#
|
10
|
+
# call-seq:
|
11
|
+
# qr => [q, r]
|
12
|
+
# qr { |q, r| ... } => block result
|
13
|
+
#
|
14
|
+
# QR factorization. Decompose the matrix into
|
15
|
+
# q * r
|
16
|
+
# where +q+ is orthogonal and +r+ is upper-triangular.
|
17
|
+
#
|
18
|
+
def qr # :yields: q, r
|
19
|
+
res = qr_private(false)
|
20
|
+
if block_given?
|
21
|
+
yield res
|
22
|
+
else
|
23
|
+
res
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# In-place QR factorization. The contents of the matrix are
|
29
|
+
# left in the state returned by dgeqrf().
|
30
|
+
#
|
31
|
+
def qr!
|
32
|
+
qr_private(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def qr_private(inplace)
|
38
|
+
mindim = Math.min(vsize, hsize)
|
39
|
+
|
40
|
+
m = XInteger.new(vsize)
|
41
|
+
n = XInteger.new(hsize)
|
42
|
+
a = inplace ? self : self.clone
|
43
|
+
lda = m
|
44
|
+
tau = DData.new(mindim)
|
45
|
+
work = DReal.new # query
|
46
|
+
lwork = XInteger.new(-1) # query
|
47
|
+
info = XInteger.new
|
48
|
+
|
49
|
+
# query
|
50
|
+
Lapack.dgeqrf(m,
|
51
|
+
n,
|
52
|
+
a,
|
53
|
+
lda,
|
54
|
+
tau,
|
55
|
+
work,
|
56
|
+
lwork,
|
57
|
+
info)
|
58
|
+
|
59
|
+
raise Diverged unless info.value == 0
|
60
|
+
lwork[0] = work[0].to_i
|
61
|
+
work = DData.new(lwork[0])
|
62
|
+
|
63
|
+
Lapack.dgeqrf(m,
|
64
|
+
n,
|
65
|
+
a,
|
66
|
+
lda,
|
67
|
+
tau,
|
68
|
+
work,
|
69
|
+
lwork,
|
70
|
+
info)
|
71
|
+
|
72
|
+
raise Diverged unless info.value == 0
|
73
|
+
|
74
|
+
return self if inplace
|
75
|
+
|
76
|
+
r = a.clone.zero_lower
|
77
|
+
|
78
|
+
iden = DMatrix.identity(vsize)
|
79
|
+
q = iden.clone
|
80
|
+
|
81
|
+
v = DMatrix.reserve(vsize, 1)
|
82
|
+
|
83
|
+
transa = Char.new("N")
|
84
|
+
transb = Char.new("T")
|
85
|
+
alpha = DReal.new
|
86
|
+
neg_one = DReal.new(-1.0)
|
87
|
+
one = XInteger.new(1)
|
88
|
+
d_one = DReal.new(1.0)
|
89
|
+
|
90
|
+
mindim.times { |p|
|
91
|
+
v.fill(0.0)
|
92
|
+
v[p] = 1.0
|
93
|
+
if p < vsize - 1
|
94
|
+
v.replace_minor(p + 1, 0, a.minor(p + 1,
|
95
|
+
p,
|
96
|
+
vsize - p - 1,
|
97
|
+
1))
|
98
|
+
end
|
99
|
+
|
100
|
+
# using dgemm instead
|
101
|
+
#h = iden - tau[p]*v*v.t
|
102
|
+
|
103
|
+
# this is not worth it ...
|
104
|
+
alpha[0] = -tau[p]
|
105
|
+
h = iden.clone
|
106
|
+
Lapack.dgemm(transa,
|
107
|
+
transb,
|
108
|
+
m, # m
|
109
|
+
m, # n
|
110
|
+
one, # k
|
111
|
+
alpha,
|
112
|
+
v, # a
|
113
|
+
m, # lda
|
114
|
+
v, # b
|
115
|
+
m, # ldb
|
116
|
+
d_one, # beta
|
117
|
+
h, # c
|
118
|
+
m) # ldc
|
119
|
+
q.postmul!(h)
|
120
|
+
}
|
121
|
+
|
122
|
+
[q, r]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2004-2008 by James M. Lawrence
|
3
|
+
#
|
4
|
+
# See LICENSE
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module Linalg
|
9
|
+
class DMatrix
|
10
|
+
#
|
11
|
+
# call-seq:
|
12
|
+
# schur => [z, t, real, imag]
|
13
|
+
# schur { |z, t, real, imag| ... } => block result
|
14
|
+
#
|
15
|
+
# Decompose into
|
16
|
+
# z * t * z.t
|
17
|
+
# where +z+ is orthogonal and +t+ is quasi upper-diagonal.
|
18
|
+
# +real+ and +imag+ contain the respective eigenvalues in the format
|
19
|
+
# described in DMatrix#eigensystem.
|
20
|
+
#
|
21
|
+
def schur # :yields: z, t, real, imag
|
22
|
+
raise DimensionError unless square?
|
23
|
+
jobvs = Char.new("V")
|
24
|
+
sort = Char.new("N")
|
25
|
+
select = XData::NULL
|
26
|
+
n = XInteger.new(vsize)
|
27
|
+
a = self.clone
|
28
|
+
lda = n
|
29
|
+
sdim = XInteger.new
|
30
|
+
wr = DMatrix.reserve(n.value, 1)
|
31
|
+
wi = DMatrix.reserve(n.value, 1)
|
32
|
+
vs = DMatrix.reserve(n.value, n.value)
|
33
|
+
ldvs = n
|
34
|
+
work = DReal.new # query
|
35
|
+
lwork = XInteger.new(-1) # query
|
36
|
+
bwork = XData::NULL
|
37
|
+
info = XInteger.new
|
38
|
+
|
39
|
+
# query
|
40
|
+
Lapack.dgees(jobvs,
|
41
|
+
sort,
|
42
|
+
select,
|
43
|
+
n,
|
44
|
+
a,
|
45
|
+
lda,
|
46
|
+
sdim,
|
47
|
+
wr,
|
48
|
+
wi,
|
49
|
+
vs,
|
50
|
+
ldvs,
|
51
|
+
work,
|
52
|
+
lwork,
|
53
|
+
bwork,
|
54
|
+
info)
|
55
|
+
|
56
|
+
raise Diverged unless info.value == 0
|
57
|
+
lwork = XInteger.new(work.value.to_i)
|
58
|
+
work = DData.new(lwork.value)
|
59
|
+
|
60
|
+
Lapack.dgees(jobvs,
|
61
|
+
sort,
|
62
|
+
select,
|
63
|
+
n,
|
64
|
+
a,
|
65
|
+
lda,
|
66
|
+
sdim,
|
67
|
+
wr,
|
68
|
+
wi,
|
69
|
+
vs,
|
70
|
+
ldvs,
|
71
|
+
work,
|
72
|
+
lwork,
|
73
|
+
bwork,
|
74
|
+
info)
|
75
|
+
|
76
|
+
raise Diverged unless info.value == 0
|
77
|
+
|
78
|
+
res = [vs, a, wr, wi]
|
79
|
+
if block_given?
|
80
|
+
yield res
|
81
|
+
else
|
82
|
+
res
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2004-2008 by James M. Lawrence
|
3
|
+
#
|
4
|
+
# See LICENSE
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module Linalg
|
9
|
+
class DMatrix
|
10
|
+
|
11
|
+
#
|
12
|
+
# call-seq: solve(a, b)
|
13
|
+
#
|
14
|
+
# Solve the linear equation
|
15
|
+
#
|
16
|
+
# a * x == b
|
17
|
+
#
|
18
|
+
# Returns the solution +x+, or raises +SingularMatrix+ if +a+ was
|
19
|
+
# singular.
|
20
|
+
#
|
21
|
+
#
|
22
|
+
def self.solve(a, b)
|
23
|
+
solve_private(a, b, false)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# call-seq: solve!(a, b)
|
28
|
+
#
|
29
|
+
# Solve the linear equation
|
30
|
+
#
|
31
|
+
# a * x == b
|
32
|
+
#
|
33
|
+
# +a+ and +b+ are *both* *overwritten*.
|
34
|
+
#
|
35
|
+
# If a unique solution is found, +a+ contains both L and U factors
|
36
|
+
# while +b+ holds the solution +x+ above.
|
37
|
+
#
|
38
|
+
# Returns the solution +x+, or raises +SingularMatrix+ if +a+ was
|
39
|
+
# singular.
|
40
|
+
#
|
41
|
+
# x = DMatrix.solve!(a, b)
|
42
|
+
# x.object_id == b.object_id
|
43
|
+
#
|
44
|
+
#
|
45
|
+
def self.solve!(a, b)
|
46
|
+
solve_private(a, b, true)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def self.solve_private(a, b, inplace)
|
52
|
+
unless a.square? and a.vsize == b.vsize
|
53
|
+
raise DimensionError
|
54
|
+
end
|
55
|
+
|
56
|
+
n = XInteger.new(a.vsize)
|
57
|
+
nrhs = XInteger.new(b.hsize)
|
58
|
+
a = inplace ? a : a.clone
|
59
|
+
lda = XInteger.new(a.vsize)
|
60
|
+
ipiv = IData.new(n.value)
|
61
|
+
b = inplace ? b : b.clone
|
62
|
+
ldb = XInteger.new(b.vsize)
|
63
|
+
info = XInteger.new
|
64
|
+
|
65
|
+
Lapack.dgesv(n,
|
66
|
+
nrhs,
|
67
|
+
a,
|
68
|
+
lda,
|
69
|
+
ipiv,
|
70
|
+
b,
|
71
|
+
ldb,
|
72
|
+
info)
|
73
|
+
|
74
|
+
raise SingularMatrix unless info.value == 0
|
75
|
+
b
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2004-2008 by James M. Lawrence
|
3
|
+
#
|
4
|
+
# See LICENSE
|
5
|
+
#
|
6
|
+
|
7
|
+
|
8
|
+
module Linalg
|
9
|
+
class DMatrix
|
10
|
+
#
|
11
|
+
# call-seq:
|
12
|
+
# singular_value_decomposition => [u, s, vt]
|
13
|
+
# singular_value_decomposition { |u, s, vt| ... } => block result
|
14
|
+
#
|
15
|
+
# Decompose the +m+ x +n+ matrix into
|
16
|
+
#
|
17
|
+
# u * s * vt
|
18
|
+
#
|
19
|
+
# where +u+ and +vt+ are orthogonal matrices and +s+ is an +m+ x +n+
|
20
|
+
# matrix whose non-diagonal elements are all zero. The diagonal
|
21
|
+
# elements of +s+ are called the <em>singular values</em> of the
|
22
|
+
# matrix. If called with
|
23
|
+
# m.singular_value_decomposition(:diagonalize => false)
|
24
|
+
# then +s+ is a vector of singular values rather than a diagonal
|
25
|
+
# matrix.
|
26
|
+
#
|
27
|
+
# May raise +Diverged+.
|
28
|
+
#
|
29
|
+
def singular_value_decomposition(opts = nil, &b)
|
30
|
+
if opts
|
31
|
+
svd_private(true, opts[:diagonalize], &b)
|
32
|
+
else
|
33
|
+
svd_private(true, true, &b)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# call-seq:
|
39
|
+
# singular_values => s
|
40
|
+
# singular_values { |s| .. } => block result
|
41
|
+
#
|
42
|
+
# Returns a vector containing the singular values of the matrix. See
|
43
|
+
# DMatrix#singular_value_decomposition.
|
44
|
+
#
|
45
|
+
def singular_values(&b)
|
46
|
+
svd_private(false, false, &b)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def svd_private(decomp, diagonalize)
|
52
|
+
jobu = Char.new(decomp ? "A" : "N")
|
53
|
+
jobvt = Char.new(decomp ? "A" : "N")
|
54
|
+
m = XInteger.new(vsize)
|
55
|
+
n = XInteger.new(hsize)
|
56
|
+
a = self.clone
|
57
|
+
lda = XInteger.new(a.vsize)
|
58
|
+
s = DMatrix.new(Math.min(vsize, hsize), 1)
|
59
|
+
u = decomp ? DMatrix.reserve(vsize, vsize) : DMatrix.reserve(1, 1)
|
60
|
+
ldu = XInteger.new(u.vsize)
|
61
|
+
vt = decomp ? DMatrix.reserve(hsize, hsize) : DMatrix.reserve(1, 1)
|
62
|
+
ldvt = XInteger.new(vt.vsize)
|
63
|
+
work = DReal.new
|
64
|
+
lwork = XInteger.new(-1) # query
|
65
|
+
info = XInteger.new
|
66
|
+
|
67
|
+
# query
|
68
|
+
Lapack.dgesvd(jobu,
|
69
|
+
jobvt,
|
70
|
+
m,
|
71
|
+
n,
|
72
|
+
a,
|
73
|
+
lda,
|
74
|
+
s,
|
75
|
+
u,
|
76
|
+
ldu,
|
77
|
+
vt,
|
78
|
+
ldvt,
|
79
|
+
work,
|
80
|
+
lwork,
|
81
|
+
info)
|
82
|
+
|
83
|
+
raise Diverged unless info.value == 0
|
84
|
+
lwork = XInteger.new(work.value.to_i)
|
85
|
+
work = DData.new(lwork.value)
|
86
|
+
|
87
|
+
Lapack.dgesvd(jobu,
|
88
|
+
jobvt,
|
89
|
+
m,
|
90
|
+
n,
|
91
|
+
a,
|
92
|
+
lda,
|
93
|
+
s,
|
94
|
+
u,
|
95
|
+
ldu,
|
96
|
+
vt,
|
97
|
+
ldvt,
|
98
|
+
work,
|
99
|
+
lwork,
|
100
|
+
info)
|
101
|
+
|
102
|
+
if decomp
|
103
|
+
if diagonalize
|
104
|
+
sm = DMatrix.new(vsize, hsize)
|
105
|
+
Math.min(vsize, hsize).times { |i|
|
106
|
+
sm[i, i] = s[i]
|
107
|
+
}
|
108
|
+
res = [u, sm, vt]
|
109
|
+
else
|
110
|
+
res = [u, s, vt]
|
111
|
+
end
|
112
|
+
else
|
113
|
+
res = s
|
114
|
+
end
|
115
|
+
|
116
|
+
if block_given?
|
117
|
+
yield res
|
118
|
+
else
|
119
|
+
res
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|