extendmatrix 0.1.0
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.
- data.tar.gz.sig +2 -0
- data/History.txt +3 -0
- data/Manifest.txt +7 -0
- data/ORIGINAL_README.txt +22 -0
- data/README.txt +32 -0
- data/Rakefile +13 -0
- data/lib/extendmatrix.rb +1048 -0
- data/spec/extendmatrix_spec.rb +357 -0
- metadata +127 -0
- metadata.gz.sig +2 -0
data.tar.gz.sig
ADDED
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/ORIGINAL_README.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Extensions to the Ruby Matrix module
|
2
|
+
====================================
|
3
|
+
|
4
|
+
This README is a small description of the work done by Cosmin Bonchis as a
|
5
|
+
Google Summer of Code 2007 project for Ruby Central Inc.
|
6
|
+
|
7
|
+
The project consists of some enhancements to the Ruby "Matrix" module and includes: LU and QR (Householder, Givens, Gram Schmidt, Hessenberg) decompositions, bidiagonalization, eigenvalue and eigenvector calculations.
|
8
|
+
|
9
|
+
This archive contains in extendmatrix.rb file the source code of the project, an implementation of mapcar used in extending matrix, and all the tests files in the "tests" directory.
|
10
|
+
|
11
|
+
The code can also be found on the RubyForge repository at http://matrix.rubyforge.org/svn/trunk/ or the project's SVN repository can be checked out through anonymous access with the following command(s).
|
12
|
+
|
13
|
+
svn checkout svn://rubyforge.org/var/svn/matrix
|
14
|
+
svn checkout http://matrix.rubyforge.org/svn/trunk/
|
15
|
+
|
16
|
+
|
17
|
+
Relevant URLs:
|
18
|
+
==============
|
19
|
+
|
20
|
+
Project sources:
|
21
|
+
http://matrix.rubyforge.org/svn/trunk/
|
22
|
+
|
data/README.txt
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
= extendmatrix
|
2
|
+
|
3
|
+
* http://github.com/clbustos/extendmatrix
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
The project consists of some enhancements to the Ruby "Matrix" module and includes: LU and QR (Householder, Givens, Gram Schmidt, Hessenberg) decompositions, bidiagonalization, eigenvalue and eigenvector calculations.
|
8
|
+
Include some aditional code to obtains marginal for rows and columns.
|
9
|
+
|
10
|
+
The original code can be found on the RubyForge repository at http://matrix.rubyforge.org/svn/trunk/ or the project's SVN repository can be checked out through anonymous access with the following command(s).
|
11
|
+
|
12
|
+
Work done by Cosmin Bonchis as a Google Summer of Code 2007 project for Ruby Central Inc.
|
13
|
+
|
14
|
+
== SYNOPSIS:
|
15
|
+
|
16
|
+
require 'matrix_extensions'
|
17
|
+
m = Matrix.new(4, 3){|i, j| i * 3 + j}
|
18
|
+
m[1, 2].should == 5
|
19
|
+
m[3, 1..2].should == Vector[10, 11]
|
20
|
+
m[0..1, 0..2].should == Matrix[[0, 1, 2], [3, 4, 5]]
|
21
|
+
|
22
|
+
== REQUIREMENTS:
|
23
|
+
|
24
|
+
* Only Ruby
|
25
|
+
|
26
|
+
== INSTALL:
|
27
|
+
|
28
|
+
* sudo gem install matrix-extensions
|
29
|
+
|
30
|
+
== LICENSE:
|
31
|
+
|
32
|
+
One of http://www.opensource.org/licenses/
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
$:.unshift(File.dirname(__FILE__)+"/lib")
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require 'extendmatrix.rb'
|
6
|
+
Hoe.plugin :git
|
7
|
+
Hoe.spec 'extendmatrix' do
|
8
|
+
self.rubyforge_name = 'ruby-statsample'
|
9
|
+
self.version = Matrix::EXTENSION_VERSION
|
10
|
+
self.developer('Cosmin Bonchis', 'cbonchis_info.uvt.ro')
|
11
|
+
end
|
12
|
+
|
13
|
+
# vim: syntax=ruby
|
data/lib/extendmatrix.rb
ADDED
@@ -0,0 +1,1048 @@
|
|
1
|
+
require 'rational'
|
2
|
+
require 'matrix'
|
3
|
+
|
4
|
+
class Vector
|
5
|
+
include Enumerable
|
6
|
+
# fix for Vector#coerce on Ruby 1.8.x
|
7
|
+
if RUBY_VERSION<="1.9.0"
|
8
|
+
alias_method :old_coerce, :coerce
|
9
|
+
def coerce(other)
|
10
|
+
case other
|
11
|
+
when Numeric
|
12
|
+
return Matrix::Scalar.new(other), self
|
13
|
+
else
|
14
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
module Norm
|
21
|
+
def Norm.sqnorm(obj, p)
|
22
|
+
sum = 0
|
23
|
+
obj.each{|x| sum += x ** p}
|
24
|
+
sum
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :length :size
|
29
|
+
alias :index :[]
|
30
|
+
#
|
31
|
+
# Returns the value of an index vector or
|
32
|
+
# a Vector with the values of a range
|
33
|
+
# v = Vector[1, 2, 3, 4]
|
34
|
+
# v[0] => 1
|
35
|
+
# v[0..2] => Vector[1, 2, 3]
|
36
|
+
#
|
37
|
+
def [](i)
|
38
|
+
case i
|
39
|
+
when Range
|
40
|
+
Vector[*to_a.slice(i)]
|
41
|
+
else
|
42
|
+
index(i)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Sets a vector value/(range of values) with a new value/(values from a vector)
|
48
|
+
# v = Vector[1, 2, 3]
|
49
|
+
# v[2] = 9 => Vector[1, 2, 9]
|
50
|
+
# v[1..2] = Vector[9, 9, 9, 9, 9] => v: Vector[1, 9, 9]
|
51
|
+
#
|
52
|
+
def []=(i, v)
|
53
|
+
case i
|
54
|
+
when Range
|
55
|
+
(self.size..i.begin - 1).each{|e| self[e] = 0} # self.size must be in the first place because the size of self can be modified
|
56
|
+
[v.size, i.entries.size].min.times {|e| self[e + i.begin] = v[e]}
|
57
|
+
(v.size + i.begin .. i.end).each {|e| self[e] = 0}
|
58
|
+
else
|
59
|
+
@elements[i]=v
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
#
|
65
|
+
# Returns a concatenated Vector
|
66
|
+
#
|
67
|
+
def concat(*args)
|
68
|
+
v = []
|
69
|
+
args.each{|x| v += x.to_a}
|
70
|
+
Vector[*v]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Changes the elements of vector and returns a Vector
|
76
|
+
#
|
77
|
+
def collect!
|
78
|
+
els = @elements.collect! {|v| yield(v)}
|
79
|
+
Vector.elements(els, false)
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Iterates the elements of a vector
|
84
|
+
#
|
85
|
+
def each
|
86
|
+
(0...size).each {|i| yield(self[i])}
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Returns the maximum element of a vector
|
92
|
+
#
|
93
|
+
def max
|
94
|
+
to_a.max
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Returns the minimum element of a vector
|
99
|
+
#
|
100
|
+
def min
|
101
|
+
to_a.min
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Returns the p-norm of a vector
|
106
|
+
#
|
107
|
+
def norm(p = 2)
|
108
|
+
Norm.sqnorm(self, p) ** (Float(1)/p)
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Returns the infinite-norm
|
113
|
+
#
|
114
|
+
def norm_inf
|
115
|
+
[min.abs, max.abs].max
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Returns a slice of vector
|
120
|
+
#
|
121
|
+
def slice(*args)
|
122
|
+
Vector[*to_a.slice(*args)]
|
123
|
+
end
|
124
|
+
|
125
|
+
def slice_set(v, b, e)
|
126
|
+
for i in b..e
|
127
|
+
self[i] = v[i-b]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Sets a slice of vector
|
133
|
+
#
|
134
|
+
def slice=(args)
|
135
|
+
case args[1]
|
136
|
+
when Range
|
137
|
+
slice_set(args[0], args[1].begin, args[1].last)
|
138
|
+
else
|
139
|
+
slice_set(args[0], args[1], args[2])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Return the vector divided by a scalar
|
145
|
+
#
|
146
|
+
def /(c)
|
147
|
+
map {|e| e.quo(c)}
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Return the matrix column coresponding to the vector transpose
|
152
|
+
#
|
153
|
+
def transpose
|
154
|
+
Matrix[self.to_a]
|
155
|
+
end
|
156
|
+
|
157
|
+
alias :t :transpose
|
158
|
+
|
159
|
+
#
|
160
|
+
# Computes the Householder vector (MC, Golub, p. 210, algorithm 5.1.1)
|
161
|
+
#
|
162
|
+
def house
|
163
|
+
s = self[1..length-1]
|
164
|
+
sigma = s.inner_product(s)
|
165
|
+
v = clone; v[0] = 1
|
166
|
+
if sigma == 0
|
167
|
+
beta = 0
|
168
|
+
else
|
169
|
+
mu = Math.sqrt(self[0] ** 2 + sigma)
|
170
|
+
if self[0] <= 0
|
171
|
+
v[0] = self[0] - mu
|
172
|
+
else
|
173
|
+
v[0] = - sigma.quo(self[0] + mu)
|
174
|
+
end
|
175
|
+
v2 = v[0] ** 2
|
176
|
+
beta = 2 * v2.quo(sigma + v2)
|
177
|
+
v /= v[0]
|
178
|
+
end
|
179
|
+
return v, beta
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
#Projection operator
|
184
|
+
#(http://en.wikipedia.org/wiki/Gram-Schmidt_process#The_Gram.E2.80.93Schmidt_process)
|
185
|
+
#
|
186
|
+
def proj(v)
|
187
|
+
vp = v.inner_product(self)
|
188
|
+
vp = Float vp if vp.is_a?(Integer)
|
189
|
+
self * (vp / inner_product(self))
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Return the vector normalized
|
194
|
+
#
|
195
|
+
def normalize
|
196
|
+
self / self.norm
|
197
|
+
end
|
198
|
+
|
199
|
+
#
|
200
|
+
# Stabilized Gram-Schmidt process
|
201
|
+
# (http://en.wikipedia.org/wiki/Gram-Schmidt_process#Algorithm)
|
202
|
+
#
|
203
|
+
def self.gram_schmidt(*vectors)
|
204
|
+
v = vectors.clone
|
205
|
+
for j in 0...v.size
|
206
|
+
for i in 0..j-1
|
207
|
+
v[j] -= v[i] * v[j].inner_product(v[i])
|
208
|
+
end
|
209
|
+
v[j] /= v[j].norm
|
210
|
+
end
|
211
|
+
v
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class Matrix
|
216
|
+
|
217
|
+
EXTENSION_VERSION="0.1.0"
|
218
|
+
include Enumerable
|
219
|
+
public_class_method :new
|
220
|
+
|
221
|
+
attr_reader :rows, :wrap
|
222
|
+
@wrap = nil
|
223
|
+
|
224
|
+
def initialize(*argv)
|
225
|
+
return initialize_old(*argv) if argv[0].is_a?(Symbol)
|
226
|
+
n, m, val = argv; val = 0 if not val
|
227
|
+
f = (block_given?)? lambda {|i,j| yield(i, j)} : lambda {|i,j| val}
|
228
|
+
init_rows((0...n).collect {|i| (0...m).collect {|j| f.call(i,j)}}, true)
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# For invoking a method
|
233
|
+
#
|
234
|
+
def initialize_old(init_method, *argv)
|
235
|
+
self.send(init_method, *argv)
|
236
|
+
end
|
237
|
+
|
238
|
+
alias :ids :[]
|
239
|
+
#
|
240
|
+
# Return a value or a vector/matrix of values depending
|
241
|
+
# if the indexes are ranges or not
|
242
|
+
# m = Matrix.new(4, 3){|i, j| i * 3 + j}
|
243
|
+
# m: 0 1 2
|
244
|
+
# 3 4 5
|
245
|
+
# 6 7 8
|
246
|
+
# 9 10 11
|
247
|
+
# m[1, 2] => 5
|
248
|
+
# m[3,1..2] => Vector[10, 11]
|
249
|
+
# m[0..1, 0..2] => Matrix[[0, 1, 2], [3, 4, 5]]
|
250
|
+
#
|
251
|
+
def [](i, j)
|
252
|
+
case i
|
253
|
+
when Range
|
254
|
+
case j
|
255
|
+
when Range
|
256
|
+
Matrix[*i.collect{|l| self.row(l)[j].to_a}]
|
257
|
+
else
|
258
|
+
column(j)[i]
|
259
|
+
end
|
260
|
+
else
|
261
|
+
case j
|
262
|
+
when Range
|
263
|
+
row(i)[j]
|
264
|
+
else
|
265
|
+
ids(i, j)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
#
|
274
|
+
# Set the values of a matrix
|
275
|
+
# m = Matrix.new(3, 3){|i, j| i * 3 + j}
|
276
|
+
# m: 0 1 2
|
277
|
+
# 3 4 5
|
278
|
+
# 6 7 8
|
279
|
+
# m[1, 2] = 9 => Matrix[[0, 1, 2], [3, 4, 9], [6, 7, 8]]
|
280
|
+
# m[2,1..2] = Vector[8, 8] => Matrix[[0, 1, 2], [3, 8, 8], [6, 7, 8]]
|
281
|
+
# m[0..1, 0..1] = Matrix[[0, 0, 0],[0, 0, 0]]
|
282
|
+
# => Matrix[[0, 0, 2], [0, 0, 8], [6, 7, 8]]
|
283
|
+
#
|
284
|
+
def []=(i, j, v)
|
285
|
+
case i
|
286
|
+
when Range
|
287
|
+
if i.entries.size == 1
|
288
|
+
self[i.begin, j] = (v.is_a?(Matrix) ? v.row(0) : v)
|
289
|
+
else
|
290
|
+
case j
|
291
|
+
when Range
|
292
|
+
if j.entries.size == 1
|
293
|
+
self[i, j.begin] = (v.is_a?(Matrix) ? v.column(0) : v)
|
294
|
+
else
|
295
|
+
i.each{|l| self.row= l, v.row(l - i.begin), j}
|
296
|
+
end
|
297
|
+
else
|
298
|
+
self.column= j, v, i
|
299
|
+
end
|
300
|
+
end
|
301
|
+
else
|
302
|
+
case j
|
303
|
+
when Range
|
304
|
+
if j.entries.size == 1
|
305
|
+
self[i, j.begin] = (v.is_a?(Vector) ? v[0] : v)
|
306
|
+
else
|
307
|
+
self.row= i, v, j
|
308
|
+
end
|
309
|
+
else
|
310
|
+
@rows[i][j] = (v.is_a?(Vector) ? v[0] : v)
|
311
|
+
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# Return a clone matrix
|
318
|
+
#
|
319
|
+
def clone
|
320
|
+
super
|
321
|
+
end
|
322
|
+
|
323
|
+
def initialize_copy(orig)
|
324
|
+
init_rows(orig.rows, true)
|
325
|
+
self.wrap=(orig.wrap)
|
326
|
+
end
|
327
|
+
|
328
|
+
|
329
|
+
class << self
|
330
|
+
#
|
331
|
+
# Creates a matrix with the given matrices as diagonal blocks
|
332
|
+
#
|
333
|
+
def diag(*args)
|
334
|
+
dsize = 0
|
335
|
+
sizes = args.collect{|e| x = (e.is_a?(Matrix)) ? e.row_size : 1; dsize += x; x}
|
336
|
+
m = Matrix.zero(dsize)
|
337
|
+
count = 0
|
338
|
+
|
339
|
+
sizes.size.times{|i|
|
340
|
+
range = count..(count+sizes[i]-1)
|
341
|
+
m[range, range] = args[i]
|
342
|
+
count += sizes[i]
|
343
|
+
}
|
344
|
+
m
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
# Tests if all the elements of two matrix are equal in delta
|
349
|
+
#
|
350
|
+
def equal_in_delta?(m0, m1, delta = 1.0e-10)
|
351
|
+
delta = delta.abs
|
352
|
+
m0.row_size.times {|i|
|
353
|
+
m0.column_size.times {|j|
|
354
|
+
x=m0[i,j]; y=m1[i,j]
|
355
|
+
return false if (x < y - delta or x > y + delta)
|
356
|
+
}
|
357
|
+
}
|
358
|
+
true
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
# Tests if all the diagonal elements of two matrix are equal in delta
|
363
|
+
#
|
364
|
+
def diag_in_delta?(m1, m0, eps = 1.0e-10)
|
365
|
+
n = m1.row_size
|
366
|
+
return false if n != m0.row_size or m1.column_size != m0.column_size
|
367
|
+
n.times{|i|
|
368
|
+
return false if (m1[i,i]-m0[i,i]).abs > eps
|
369
|
+
}
|
370
|
+
true
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
#
|
375
|
+
# Returns the matrix divided by a scalar
|
376
|
+
#
|
377
|
+
def quo(v)
|
378
|
+
map {|e| e.quo(v)}
|
379
|
+
end
|
380
|
+
|
381
|
+
#
|
382
|
+
# quo seems always desirable
|
383
|
+
#
|
384
|
+
alias :/ :quo
|
385
|
+
|
386
|
+
#
|
387
|
+
# Set de values of a matrix and the value of wrap property
|
388
|
+
#
|
389
|
+
def set(m)
|
390
|
+
0.upto(m.row_size - 1) do |i|
|
391
|
+
0.upto(m.column_size - 1) do |j|
|
392
|
+
self[i, j] = m[i, j]
|
393
|
+
end
|
394
|
+
end
|
395
|
+
self.wrap = m.wrap
|
396
|
+
end
|
397
|
+
|
398
|
+
def wraplate(ijwrap = "")
|
399
|
+
"class << self
|
400
|
+
def [](i, j)
|
401
|
+
#{ijwrap}; @rows[i][j]
|
402
|
+
end
|
403
|
+
|
404
|
+
def []=(i, j, v)
|
405
|
+
#{ijwrap}; @rows[i][j] = v
|
406
|
+
end
|
407
|
+
end"
|
408
|
+
end
|
409
|
+
|
410
|
+
#
|
411
|
+
# Set wrap feature of a matrix
|
412
|
+
#
|
413
|
+
def wrap=(mode = :torus)
|
414
|
+
case mode
|
415
|
+
when :torus then eval(wraplate("i %= row_size; j %= column_size"))
|
416
|
+
when :h_cylinder then eval(wraplate("i %= row_size"))
|
417
|
+
when :v_cylinder then eval(wraplate("j %= column_size"))
|
418
|
+
when :nil then eval(wraplate)
|
419
|
+
end
|
420
|
+
@wrap = mode
|
421
|
+
end
|
422
|
+
|
423
|
+
#
|
424
|
+
# Returns the maximum length of column elements
|
425
|
+
#
|
426
|
+
def max_len_column(j)
|
427
|
+
column_collect(j) {|x| x.to_s.length}.max
|
428
|
+
end
|
429
|
+
|
430
|
+
#
|
431
|
+
# Returns a list with the maximum lengths
|
432
|
+
#
|
433
|
+
def cols_len
|
434
|
+
(0...column_size).collect {|j| max_len_column(j)}
|
435
|
+
end
|
436
|
+
|
437
|
+
#
|
438
|
+
# Returns a string for nice printing matrix
|
439
|
+
#
|
440
|
+
def to_s(mode = :pretty, len_col = 3)
|
441
|
+
return super if empty?
|
442
|
+
if mode == :pretty
|
443
|
+
clen = cols_len
|
444
|
+
to_a.collect {|r|
|
445
|
+
i=0
|
446
|
+
r.map {|x|
|
447
|
+
l=clen[i]
|
448
|
+
i+=1
|
449
|
+
format("%#{l}s ", x.to_s)
|
450
|
+
} << "\n"
|
451
|
+
}.join("")
|
452
|
+
else
|
453
|
+
i = 0; s = ""; cs = column_size
|
454
|
+
each do |e|
|
455
|
+
i = (i + 1) % cs
|
456
|
+
s += format("%#{len_col}s ", e.to_s)
|
457
|
+
s += "\n" if i == 0
|
458
|
+
end
|
459
|
+
s
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
#
|
464
|
+
# Iterate the elements of a matrix
|
465
|
+
#
|
466
|
+
def each
|
467
|
+
@rows.each {|x| x.each {|e| yield(e)}}
|
468
|
+
nil
|
469
|
+
end
|
470
|
+
|
471
|
+
#
|
472
|
+
# a hided module of Matrix
|
473
|
+
module MMatrix
|
474
|
+
def self.default_block(block)
|
475
|
+
block ? lambda { |i| block.call(i) } : lambda {|i| i }
|
476
|
+
end
|
477
|
+
|
478
|
+
#
|
479
|
+
# Returns:
|
480
|
+
# 1) the index of row/column and
|
481
|
+
# 2) the values Vector for changing the row/column and
|
482
|
+
# 3) the range of changes
|
483
|
+
#
|
484
|
+
def self.id_vect_range(args, l)
|
485
|
+
i = args[0] # the column(/the row) to be change
|
486
|
+
vect = args[1] # the values vector
|
487
|
+
|
488
|
+
case args.size
|
489
|
+
when 3 then range = args[2] # the range of the elements to be change
|
490
|
+
when 4 then range = args[2]..args[3] #the range by borders
|
491
|
+
else range = 0...l
|
492
|
+
end
|
493
|
+
return i, vect, range
|
494
|
+
end
|
495
|
+
|
496
|
+
end
|
497
|
+
|
498
|
+
#
|
499
|
+
# Returns an array with the elements collected from the row "i".
|
500
|
+
# When a block is given, the elements of that vector are iterated.
|
501
|
+
#
|
502
|
+
def row_collect(i, &block)
|
503
|
+
f = MMatrix.default_block(block)
|
504
|
+
@rows[i].collect {|e| f.call(e)}
|
505
|
+
end
|
506
|
+
|
507
|
+
#
|
508
|
+
# Returns row vector number "i" like Matrix.row as a Vector.
|
509
|
+
# When the block is given, the elements of row "i" are modified
|
510
|
+
#
|
511
|
+
def row!(i)
|
512
|
+
if block_given?
|
513
|
+
@rows[i].collect! {|e| yield e }
|
514
|
+
else
|
515
|
+
Vector.elements(@rows[i], false)
|
516
|
+
end
|
517
|
+
end
|
518
|
+
alias :row_collect! :row!
|
519
|
+
|
520
|
+
#
|
521
|
+
# Returns an array with the elements collected from the column "j".
|
522
|
+
# When a block is given, the elements of that vector are iterated.
|
523
|
+
#
|
524
|
+
def column_collect(j, &block)
|
525
|
+
f = MMatrix.default_block(block)
|
526
|
+
(0...row_size).collect {|r| f.call(self[r, j])}
|
527
|
+
end
|
528
|
+
|
529
|
+
#
|
530
|
+
# Returns column vector number "j" as a Vector.
|
531
|
+
# When the block is given, the elements of column "j" are mmodified
|
532
|
+
#
|
533
|
+
def column!(j)
|
534
|
+
if block_given?
|
535
|
+
(0...row_size).collect { |i| @rows[i][j] = yield @rows[i][j] }
|
536
|
+
else
|
537
|
+
column(j)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
alias :column_collect! :column!
|
541
|
+
|
542
|
+
#
|
543
|
+
# Set a certain column with the values of a Vector
|
544
|
+
# m = Matrix.new(3, 3){|i, j| i * 3 + j + 1}
|
545
|
+
# m.column= 1, Vector[1, 1, 1], 1..2
|
546
|
+
# m => 1 2 3
|
547
|
+
# 4 1 6
|
548
|
+
# 7 1 9
|
549
|
+
#
|
550
|
+
def column=(args)
|
551
|
+
m = row_size
|
552
|
+
c, v, r = MMatrix.id_vect_range(args, m)
|
553
|
+
(m..r.begin - 1).each{|i| self[i, c] = 0}
|
554
|
+
[v.size, r.entries.size].min.times{|i| self[i + r.begin, c] = v[i]}
|
555
|
+
((v.size + r.begin)..r.entries.last).each {|i| self[i, c] = 0}
|
556
|
+
end
|
557
|
+
|
558
|
+
#
|
559
|
+
# Set a certain row with the values of a Vector
|
560
|
+
# m = Matrix.new(3, 3){|i, j| i * 3 + j + 1}
|
561
|
+
# m.row= 0, Vector[0, 0], 1..2
|
562
|
+
# m => 1 0 0
|
563
|
+
# 4 5 6
|
564
|
+
# 7 8 9
|
565
|
+
#
|
566
|
+
def row=(args)
|
567
|
+
i, val, range = MMatrix.id_vect_range(args, column_size)
|
568
|
+
row!(i)[range] = val
|
569
|
+
end
|
570
|
+
|
571
|
+
def norm(p = 2)
|
572
|
+
Vector::Norm.sqnorm(self, p) ** (Float(1)/p)
|
573
|
+
end
|
574
|
+
|
575
|
+
def norm_frobenius
|
576
|
+
norm
|
577
|
+
end
|
578
|
+
alias :normF :norm_frobenius
|
579
|
+
|
580
|
+
#
|
581
|
+
# Tests if the matrix is empty or not
|
582
|
+
#
|
583
|
+
def empty?
|
584
|
+
@rows.empty? if @rows
|
585
|
+
end
|
586
|
+
|
587
|
+
#
|
588
|
+
# Returns the row/s of matrix as a Matrix
|
589
|
+
#
|
590
|
+
def row2matrix(r)
|
591
|
+
a = self.send(:row, r).to_a
|
592
|
+
if r.is_a?(Range) and r.entries.size > 1
|
593
|
+
return Matrix[*a]
|
594
|
+
else
|
595
|
+
return Matrix[a]
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
#
|
600
|
+
# Returns the colomn/s of matrix as a Matrix
|
601
|
+
#
|
602
|
+
def column2matrix(c)
|
603
|
+
a = self.send(:column, c).to_a
|
604
|
+
if c.is_a?(Range) and c.entries.size > 1
|
605
|
+
return Matrix[*a]
|
606
|
+
else
|
607
|
+
return Matrix[*a.collect{|x| [x]}]
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
# Calculate marginal of rows
|
612
|
+
def row_sum
|
613
|
+
(0...row_size).collect {|i|
|
614
|
+
row(i).to_a.inject(0) {|a,v| a+v}
|
615
|
+
}
|
616
|
+
end
|
617
|
+
# Calculate marginal of columns
|
618
|
+
def column_sum
|
619
|
+
(0...column_size).collect {|i|
|
620
|
+
column(i).to_a.inject(0) {|a,v| a+v}
|
621
|
+
}
|
622
|
+
end
|
623
|
+
# Calculate sum of cells
|
624
|
+
def total_sum
|
625
|
+
row_sum.inject(0){|a,v| a+v}
|
626
|
+
end
|
627
|
+
|
628
|
+
module LU
|
629
|
+
#
|
630
|
+
# Return the Gauss vector, MC, Golub, 3.2.1 Gauss Transformation, p94
|
631
|
+
#
|
632
|
+
def self.gauss_vector(mat, k)
|
633
|
+
t = mat.column2matrix(k)
|
634
|
+
tk = t[k, 0]
|
635
|
+
(0..k).each{|i| t[i, 0] = 0}
|
636
|
+
return t if tk == 0
|
637
|
+
(k+1...mat.row_size).each{|i| t[i, 0] = t[i, 0].to_f / tk}
|
638
|
+
t
|
639
|
+
end
|
640
|
+
|
641
|
+
#
|
642
|
+
# Return the Gauss transformation matrix: M_k = I - tau * e_k^T
|
643
|
+
#
|
644
|
+
def self.gauss(mat, k)
|
645
|
+
i = Matrix.I(mat.column_size)
|
646
|
+
tau = gauss_vector(mat, k)
|
647
|
+
e = i.row2matrix(k)
|
648
|
+
i - tau * e
|
649
|
+
end
|
650
|
+
|
651
|
+
#
|
652
|
+
# LU factorization: A = LU
|
653
|
+
# where L is lower triangular and U is upper triangular
|
654
|
+
#
|
655
|
+
def self.factorization(mat)
|
656
|
+
u = mat.clone
|
657
|
+
n = u.column_size
|
658
|
+
i = Matrix.I(n)
|
659
|
+
l = i.clone
|
660
|
+
(n-1).times {|k|
|
661
|
+
mk = gauss(u, k)
|
662
|
+
u = mk * u # M_{n-1} * ... * M_1 * A = U
|
663
|
+
l += i - mk # L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
|
664
|
+
}
|
665
|
+
return l, u
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
#
|
670
|
+
# Return the upper triangular matrix of LU factorization
|
671
|
+
# M_{n-1} * ... * M_1 * A = U
|
672
|
+
#
|
673
|
+
def U
|
674
|
+
LU.factorization(self)[1]
|
675
|
+
end
|
676
|
+
|
677
|
+
#
|
678
|
+
# Return the lower triangular matrix of LU factorization
|
679
|
+
# L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
|
680
|
+
#
|
681
|
+
def L
|
682
|
+
LU.factorization(self)[0]
|
683
|
+
end
|
684
|
+
|
685
|
+
module Householder
|
686
|
+
#
|
687
|
+
# a QR factorization that uses Householder transformation
|
688
|
+
# Q^T * A = R
|
689
|
+
# MC, Golub & van Loan, pg 224, 5.2.1 Householder QR
|
690
|
+
#
|
691
|
+
def self.QR(mat)
|
692
|
+
h = []
|
693
|
+
a = mat.clone
|
694
|
+
m = a.row_size
|
695
|
+
n = a.column_size
|
696
|
+
n.times{|j|
|
697
|
+
v, beta = a[j..m - 1, j].house
|
698
|
+
|
699
|
+
h[j] = Matrix.diag(Matrix.I(j), Matrix.I(m-j)- beta * (v * v.t))
|
700
|
+
|
701
|
+
a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
|
702
|
+
a[(j+1)..m-1,j] = v[2..(m-j)] if j < m - 1 }
|
703
|
+
h
|
704
|
+
end
|
705
|
+
|
706
|
+
#
|
707
|
+
# From the essential part of Householder vector
|
708
|
+
# it returns the coresponding upper(U_j)/lower(V_j) matrix
|
709
|
+
#
|
710
|
+
def self.bidiagUV(essential, dim, beta)
|
711
|
+
v = Vector.concat(Vector[1], essential)
|
712
|
+
dimv = v.size
|
713
|
+
Matrix.diag(Matrix.I(dim - dimv), Matrix.I(dimv) - beta * (v * v.t) )
|
714
|
+
end
|
715
|
+
|
716
|
+
#
|
717
|
+
# Householder Bidiagonalization algorithm. MC, Golub, pg 252, Algorithm 5.4.2
|
718
|
+
# Returns the matrices U_B and V_B such that: U_B^T * A * V_B = B,
|
719
|
+
# where B is upper bidiagonal.
|
720
|
+
#
|
721
|
+
def self.bidiag(mat)
|
722
|
+
a = mat.clone
|
723
|
+
m = a.row_size
|
724
|
+
n = a.column_size
|
725
|
+
ub = Matrix.I(m)
|
726
|
+
vb = Matrix.I(n)
|
727
|
+
n.times{|j|
|
728
|
+
v, beta = a[j..m-1,j].house
|
729
|
+
a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
|
730
|
+
a[j+1..m-1, j] = v[1..(m-j-1)]
|
731
|
+
ub *= bidiagUV(a[j+1..m-1,j], m, beta) #Ub = U_1 * U_2 * ... * U_n
|
732
|
+
if j < n - 2
|
733
|
+
v, beta = (a[j, j+1..n-1]).house
|
734
|
+
a[j..m-1, j+1..n-1] = a[j..m-1, j+1..n-1] * (Matrix.I(n-j-1) - beta * (v * v.t))
|
735
|
+
a[j, j+2..n-1] = v[1..n-j-2]
|
736
|
+
vb *= bidiagUV(a[j, j+2..n-1], n, beta) #Vb = V_1 * U_2 * ... * V_n-2
|
737
|
+
end }
|
738
|
+
return ub, vb
|
739
|
+
end
|
740
|
+
|
741
|
+
#
|
742
|
+
#Householder Reduction to Hessenberg Form
|
743
|
+
#
|
744
|
+
def self.toHessenberg(mat)
|
745
|
+
h = mat.clone
|
746
|
+
n = h.row_size
|
747
|
+
u0 = Matrix.I(n)
|
748
|
+
for k in (0...n - 2)
|
749
|
+
v, beta = h[k+1..n-1, k].house #the householder matrice part
|
750
|
+
houseV = Matrix.I(n-k-1) - beta * (v * v.t)
|
751
|
+
u0 *= Matrix.diag(Matrix.I(k+1), houseV)
|
752
|
+
h[k+1..n-1, k..n-1] = houseV * h[k+1..n-1, k..n-1]
|
753
|
+
h[0..n-1, k+1..n-1] = h[0..n-1, k+1..n-1] * houseV
|
754
|
+
end
|
755
|
+
return h, u0
|
756
|
+
end
|
757
|
+
|
758
|
+
|
759
|
+
end #end of Householder module
|
760
|
+
|
761
|
+
#
|
762
|
+
# Returns the upper bidiagonal matrix obtained with Householder Bidiagonalization algorithm
|
763
|
+
#
|
764
|
+
def bidiagonal
|
765
|
+
ub, vb = Householder.bidiag(self)
|
766
|
+
ub.t * self * vb
|
767
|
+
end
|
768
|
+
|
769
|
+
#
|
770
|
+
# Returns the orthogonal matrix Q of Householder QR factorization
|
771
|
+
# where Q = H_1 * H_2 * H_3 * ... * H_n,
|
772
|
+
#
|
773
|
+
def houseQ
|
774
|
+
h = Householder.QR(self)
|
775
|
+
q = h[0]
|
776
|
+
(1...h.size).each{|i| q *= h[i]}
|
777
|
+
q
|
778
|
+
end
|
779
|
+
|
780
|
+
#
|
781
|
+
# Returns the matrix R of Householder QR factorization
|
782
|
+
# R = H_n * H_n-1 * ... * H_1 * A is an upper triangular matrix
|
783
|
+
#
|
784
|
+
def houseR
|
785
|
+
h = Householder.QR(self)
|
786
|
+
r = self.clone
|
787
|
+
h.size.times{|i| r = h[i] * r}
|
788
|
+
r
|
789
|
+
end
|
790
|
+
|
791
|
+
#
|
792
|
+
# Modified Gram Schmidt QR factorization (MC, Golub, p. 232)
|
793
|
+
# A = Q_1 * R_1
|
794
|
+
#
|
795
|
+
def gram_schmidt
|
796
|
+
a = clone
|
797
|
+
n = column_size
|
798
|
+
m = row_size
|
799
|
+
q = Matrix.new(m, n){0}
|
800
|
+
r = Matrix.zero(n)
|
801
|
+
for k in 0...n
|
802
|
+
r[k,k] = a[0...m, k].norm
|
803
|
+
q[0...m, k] = a[0...m, k] / r[k, k]
|
804
|
+
for j in (k+1)...n
|
805
|
+
r[k, j] = q[0...m, k].t * a[0...m, j]
|
806
|
+
a[0...m, j] -= q[0...m, k] * r[k, j]
|
807
|
+
end
|
808
|
+
end
|
809
|
+
return q, r
|
810
|
+
end
|
811
|
+
|
812
|
+
#
|
813
|
+
# Returns the Q_1 matrix of Modified Gram Schmidt algorithm
|
814
|
+
# Q_1 has orthonormal columns
|
815
|
+
#
|
816
|
+
def gram_schmidtQ
|
817
|
+
gram_schmidt[0]
|
818
|
+
end
|
819
|
+
|
820
|
+
#
|
821
|
+
# Returns the R_1 upper triangular matrix of Modified Gram Schmidt algorithm
|
822
|
+
#
|
823
|
+
def gram_schmidtR
|
824
|
+
gram_schmidt[1]
|
825
|
+
end
|
826
|
+
|
827
|
+
|
828
|
+
module Givens
|
829
|
+
#
|
830
|
+
# Returns the values "c and s" of a Given rotation
|
831
|
+
# MC, Golub, pg 216, Alghorithm 5.1.3
|
832
|
+
#
|
833
|
+
def self.givens(a, b)
|
834
|
+
if b == 0
|
835
|
+
c = 0; s = 0
|
836
|
+
else
|
837
|
+
if b.abs > a.abs
|
838
|
+
tau = Float(-a)/b; s = 1/Math.sqrt(1+tau**2); c = s * tau
|
839
|
+
else
|
840
|
+
tau = Float(-b)/a; c = 1/Math.sqrt(1+tau**2); s = c * tau
|
841
|
+
end
|
842
|
+
end
|
843
|
+
return c, s
|
844
|
+
end
|
845
|
+
|
846
|
+
#
|
847
|
+
# a QR factorization using Givens rotation
|
848
|
+
# Computes the upper triangular matrix R and the orthogonal matrix Q
|
849
|
+
# where Q^t A = R (MC, Golub, p227 algorithm 5.2.2)
|
850
|
+
#
|
851
|
+
def self.QR(mat)
|
852
|
+
r = mat.clone
|
853
|
+
m = r.row_size
|
854
|
+
n = r.column_size
|
855
|
+
q = Matrix.I(m)
|
856
|
+
n.times{|j|
|
857
|
+
m-1.downto(j+1){|i|
|
858
|
+
c, s = givens(r[i - 1, j], r[i, j])
|
859
|
+
qt = Matrix.I(m); qt[i-1..i, i-1..i] = Matrix[[c, s],[-s, c]]
|
860
|
+
q *= qt
|
861
|
+
r[i-1..i, j..n-1] = Matrix[[c, -s],[s, c]] * r[i-1..i, j..n-1]}}
|
862
|
+
return r, q
|
863
|
+
end
|
864
|
+
|
865
|
+
end
|
866
|
+
|
867
|
+
#
|
868
|
+
# Returns the upper triunghiular matrix R of a Givens QR factorization
|
869
|
+
#
|
870
|
+
def givensR
|
871
|
+
Givens.QR(self)[0]
|
872
|
+
end
|
873
|
+
|
874
|
+
#
|
875
|
+
# Returns the orthogonal matrix Q of Givens QR factorization.
|
876
|
+
# Q = G_1 * ... * G_t where G_j is the j'th Givens rotation
|
877
|
+
# and 't' is the total number of rotations
|
878
|
+
#
|
879
|
+
def givensQ
|
880
|
+
Givens.QR(self)[1]
|
881
|
+
end
|
882
|
+
|
883
|
+
module Hessenberg
|
884
|
+
#
|
885
|
+
# the matrix must be an upper R^(n x n) Hessenberg matrix
|
886
|
+
#
|
887
|
+
def self.QR(mat)
|
888
|
+
r = mat.clone
|
889
|
+
n = r.row_size
|
890
|
+
q = Matrix.I(n)
|
891
|
+
for j in (0...n-1)
|
892
|
+
c, s = Givens.givens(r[j,j], r[j+1, j])
|
893
|
+
cs = Matrix[[c, s], [-s, c]]
|
894
|
+
q *= Matrix.diag(Matrix.I(j), cs, Matrix.I(n - j - 2))
|
895
|
+
r[j..j+1, j..n-1] = cs.t * r[j..j+1, j..n-1]
|
896
|
+
end
|
897
|
+
return q, r
|
898
|
+
end
|
899
|
+
end
|
900
|
+
|
901
|
+
#
|
902
|
+
# Returns the orthogonal matrix Q of Hessenberg QR factorization
|
903
|
+
# Q = G_1 *...* G_(n-1) where G_j is the Givens rotation G_j = G(j, j+1, omega_j)
|
904
|
+
#
|
905
|
+
def hessenbergQ
|
906
|
+
Hessenberg.QR(self)[0]
|
907
|
+
end
|
908
|
+
|
909
|
+
#
|
910
|
+
# Returns the upper triunghiular matrix R of a Hessenberg QR factorization
|
911
|
+
#
|
912
|
+
def hessenbergR
|
913
|
+
Hessenberg.QR(self)[1]
|
914
|
+
end
|
915
|
+
|
916
|
+
#
|
917
|
+
# Return an upper Hessenberg matrix obtained with Householder reduction to Hessenberg Form algorithm
|
918
|
+
#
|
919
|
+
def hessenberg_form_H
|
920
|
+
Householder.toHessenberg(self)[0]
|
921
|
+
end
|
922
|
+
|
923
|
+
#
|
924
|
+
# The real Schur decomposition.
|
925
|
+
# The eigenvalues are aproximated in diagonal elements of the real Schur decomposition matrix
|
926
|
+
#
|
927
|
+
def realSchur(eps = 1.0e-10, steps = 100)
|
928
|
+
h = self.hessenberg_form_H
|
929
|
+
h1 = Matrix[]
|
930
|
+
i = 0
|
931
|
+
loop do
|
932
|
+
h1 = h.hessenbergR * h.hessenbergQ
|
933
|
+
break if Matrix.diag_in_delta?(h1, h, eps) or steps <= 0
|
934
|
+
h = h1.clone
|
935
|
+
steps -= 1
|
936
|
+
i += 1
|
937
|
+
end
|
938
|
+
h1
|
939
|
+
end
|
940
|
+
|
941
|
+
|
942
|
+
module Jacobi
|
943
|
+
#
|
944
|
+
# Returns the nurm of the off-diagonal element
|
945
|
+
#
|
946
|
+
def self.off(a)
|
947
|
+
n = a.row_size
|
948
|
+
sum = 0
|
949
|
+
n.times{|i| n.times{|j| sum += a[i, j]**2 if j != i}}
|
950
|
+
Math.sqrt(sum)
|
951
|
+
end
|
952
|
+
|
953
|
+
#
|
954
|
+
# Returns the index pair (p, q) with 1<= p < q <= n and A[p, q] is the maximum in absolute value
|
955
|
+
#
|
956
|
+
def self.max(a)
|
957
|
+
n = a.row_size
|
958
|
+
max = 0
|
959
|
+
p = 0
|
960
|
+
q = 0
|
961
|
+
n.times{|i|
|
962
|
+
((i+1)...n).each{|j|
|
963
|
+
val = a[i, j].abs
|
964
|
+
if val > max
|
965
|
+
max = val
|
966
|
+
p = i
|
967
|
+
q = j
|
968
|
+
end }}
|
969
|
+
return p, q
|
970
|
+
end
|
971
|
+
|
972
|
+
#
|
973
|
+
# Compute the cosine-sine pair (c, s) for the element A[p, q]
|
974
|
+
#
|
975
|
+
def self.sym_schur2(a, p, q)
|
976
|
+
if a[p, q] != 0
|
977
|
+
tau = Float(a[q, q] - a[p, p])/(2 * a[p, q])
|
978
|
+
if tau >= 0
|
979
|
+
t = 1./(tau + Math.sqrt(1 + tau ** 2))
|
980
|
+
else
|
981
|
+
t = -1./(-tau + Math.sqrt(1 + tau ** 2))
|
982
|
+
end
|
983
|
+
c = 1./Math.sqrt(1 + t ** 2)
|
984
|
+
s = t * c
|
985
|
+
else
|
986
|
+
c = 1
|
987
|
+
s = 0
|
988
|
+
end
|
989
|
+
return c, s
|
990
|
+
end
|
991
|
+
|
992
|
+
#
|
993
|
+
# Returns the Jacobi rotation matrix
|
994
|
+
#
|
995
|
+
def self.J(p, q, c, s, n)
|
996
|
+
j = Matrix.I(n)
|
997
|
+
j[p,p] = c; j[p, q] = s
|
998
|
+
j[q,p] = -s; j[q, q] = c
|
999
|
+
j
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
#
|
1004
|
+
# Classical Jacobi 8.4.3 Golub & van Loan
|
1005
|
+
#
|
1006
|
+
def cJacobi(tol = 1.0e-10)
|
1007
|
+
a = self.clone
|
1008
|
+
n = row_size
|
1009
|
+
v = Matrix.I(n)
|
1010
|
+
eps = tol * a.normF
|
1011
|
+
while Jacobi.off(a) > eps
|
1012
|
+
p, q = Jacobi.max(a)
|
1013
|
+
c, s = Jacobi.sym_schur2(a, p, q)
|
1014
|
+
#print "\np:#{p} q:#{q} c:#{c} s:#{s}\n"
|
1015
|
+
j = Jacobi.J(p, q, c, s, n)
|
1016
|
+
a = j.t * a * j
|
1017
|
+
v = v * j
|
1018
|
+
end
|
1019
|
+
return a, v
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
#
|
1023
|
+
# Returns the aproximation matrix computed with Classical Jacobi algorithm.
|
1024
|
+
# The aproximate eigenvalues values are in the diagonal of the matrix A.
|
1025
|
+
#
|
1026
|
+
def cJacobiA(tol = 1.0e-10)
|
1027
|
+
cJacobi(tol)[0]
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
#
|
1031
|
+
# Returns a Vector with the eigenvalues aproximated values.
|
1032
|
+
# The eigenvalues are computed with the Classic Jacobi Algorithm.
|
1033
|
+
#
|
1034
|
+
def eigenvaluesJacobi
|
1035
|
+
a = cJacobiA
|
1036
|
+
Vector[*(0...row_size).collect{|i| a[i, i]}]
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
#
|
1040
|
+
# Returns the orthogonal matrix obtained with the Jacobi eigenvalue algorithm.
|
1041
|
+
# The columns of V are the eigenvector.
|
1042
|
+
#
|
1043
|
+
def cJacobiV(tol = 1.0e-10)
|
1044
|
+
cJacobi(tol)[1]
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
|