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 ADDED
@@ -0,0 +1,2 @@
1
+ �R&ש��pV�%0���T�ljਾ�p��! }r)� ?�V=|��G�שv�͜+[��ȴ{��Zz�=*+� ��ڕ)�d������P��vgr H��8>�� ]�>x܆Ƕ��w�zp����-�����(
2
+ ۺן�>����Jm|���
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.0.60 / 2010-05-04
2
+
3
+ * First gem version
data/Manifest.txt ADDED
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ ORIGINAL_README.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/extendmatrix.rb
7
+ spec/extendmatrix_spec.rb
@@ -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
@@ -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
+